@barefootjs/jsx 0.26.2 → 0.26.4
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/adapters/interface.d.ts +29 -0
- package/dist/adapters/interface.d.ts.map +1 -1
- package/dist/adapters/jsx-adapter.d.ts +9 -0
- package/dist/adapters/jsx-adapter.d.ts.map +1 -1
- package/dist/adapters/loop-bound-names.d.ts.map +1 -1
- package/dist/adapters/parsed-expr-emitter.d.ts +0 -10
- package/dist/adapters/parsed-expr-emitter.d.ts.map +1 -1
- package/dist/adapters/test-adapter.d.ts.map +1 -1
- package/dist/analyzer-context.d.ts +11 -1
- package/dist/analyzer-context.d.ts.map +1 -1
- package/dist/analyzer.d.ts +40 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/expression-parser.d.ts.map +1 -1
- package/dist/index.js +1086 -269
- package/dist/ir-to-client-js/build-references.d.ts.map +1 -1
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts +14 -1
- package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-component-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-composite-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-event-delegation.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-inner-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/event-delegation.d.ts +16 -1
- package/dist/ir-to-client-js/control-flow/plan/event-delegation.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +39 -0
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/shared.d.ts +12 -1
- package/dist/ir-to-client-js/control-flow/shared.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/event-delegation.d.ts +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/event-delegation.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts +19 -0
- package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts +67 -1
- package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
- package/dist/ir-to-client-js/imports.d.ts +2 -2
- package/dist/ir-to-client-js/imports.d.ts.map +1 -1
- package/dist/ir-to-client-js/plan/build-static-array-child-init.d.ts.map +1 -1
- package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
- package/dist/ir-to-client-js/types.d.ts +29 -4
- package/dist/ir-to-client-js/types.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/loop-destructure.d.ts.map +1 -1
- package/dist/strip-types.d.ts +18 -0
- package/dist/strip-types.d.ts.map +1 -1
- package/dist/types.d.ts +143 -32
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +277 -33
- package/src/__tests__/client-js-generation.test.ts +18 -5
- package/src/__tests__/compiler-runtime-contract.test.ts +4 -4
- package/src/__tests__/compiler-stress-1244.test.ts +12 -1
- package/src/__tests__/delegated-handler-preamble.test.ts +153 -0
- package/src/__tests__/event-delegation-scope-2367.test.ts +108 -0
- package/src/__tests__/flatmap-segments.test.ts +262 -0
- package/src/__tests__/map-arbitrary-body.test.ts +226 -0
- package/src/__tests__/map-body-no-silent-divergence.test.ts +405 -0
- package/src/__tests__/map-multi-return-body.test.ts +172 -0
- package/src/__tests__/preamble-region-patch.test.ts +150 -0
- package/src/__tests__/static-loop-csr-materialize.test.ts +3 -2
- package/src/__tests__/unsupported-expression.test.ts +20 -2
- package/src/adapters/interface.ts +40 -0
- package/src/adapters/jsx-adapter.ts +10 -0
- package/src/adapters/loop-bound-names.ts +6 -2
- package/src/adapters/parsed-expr-emitter.ts +5 -10
- package/src/adapters/test-adapter.ts +10 -0
- package/src/analyzer-context.ts +35 -1
- package/src/analyzer.ts +162 -24
- package/src/compiler.ts +2 -2
- package/src/expression-parser.ts +27 -19
- package/src/ir-to-client-js/build-references.ts +19 -2
- package/src/ir-to-client-js/collect-elements.ts +69 -13
- package/src/ir-to-client-js/control-flow/plan/branch-loop.ts +14 -1
- package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +18 -5
- package/src/ir-to-client-js/control-flow/plan/build-component-loop.ts +4 -0
- package/src/ir-to-client-js/control-flow/plan/build-composite-loop.ts +15 -2
- package/src/ir-to-client-js/control-flow/plan/build-event-delegation.ts +32 -3
- package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +26 -3
- package/src/ir-to-client-js/control-flow/plan/build-loop.ts +53 -2
- package/src/ir-to-client-js/control-flow/plan/event-delegation.ts +16 -1
- package/src/ir-to-client-js/control-flow/plan/loop.ts +40 -0
- package/src/ir-to-client-js/control-flow/shared.ts +28 -2
- package/src/ir-to-client-js/control-flow/stringify/branch-loop.ts +25 -3
- package/src/ir-to-client-js/control-flow/stringify/event-delegation.ts +76 -21
- package/src/ir-to-client-js/control-flow/stringify/loop.ts +65 -1
- package/src/ir-to-client-js/control-flow.ts +11 -0
- package/src/ir-to-client-js/html-template.ts +244 -39
- package/src/ir-to-client-js/imports.ts +1 -1
- package/src/ir-to-client-js/plan/build-static-array-child-init.ts +22 -10
- package/src/ir-to-client-js/reactivity.ts +6 -0
- package/src/ir-to-client-js/types.ts +28 -3
- package/src/jsx-to-ir.ts +1059 -167
- package/src/loop-destructure.ts +9 -5
- package/src/rich-type-refusal.ts +6 -2
- package/src/strip-types.ts +47 -0
- package/src/types.ts +159 -35
package/dist/index.js
CHANGED
|
@@ -250,6 +250,7 @@ var UNSUPPORTED_METHODS = new Set([
|
|
|
250
250
|
"some",
|
|
251
251
|
"forEach",
|
|
252
252
|
"flatMap",
|
|
253
|
+
"fill",
|
|
253
254
|
"charAt",
|
|
254
255
|
"charCodeAt",
|
|
255
256
|
"codePointAt",
|
|
@@ -3251,7 +3252,7 @@ function templateAttrExpr(attrName, valExpr, presenceOrUndefined) {
|
|
|
3251
3252
|
return `\${((v) => v != null ? 'style="' + ${escapeAttrValueExpr("v")} + '"' : '')(styleToCss(${valExpr}))}`;
|
|
3252
3253
|
}
|
|
3253
3254
|
if (attrName === "data-key" || attrName.startsWith("data-key-")) {
|
|
3254
|
-
return `${attrName}="\${${valExpr}}"`;
|
|
3255
|
+
return `${attrName}="\${${escapeAttrValueExpr(valExpr)}}"`;
|
|
3255
3256
|
}
|
|
3256
3257
|
return `\${(${valExpr}) != null ? '${attrName}="' + ${escapeAttrValueExpr(valExpr)} + '"' : ''}`;
|
|
3257
3258
|
}
|
|
@@ -3380,6 +3381,86 @@ function buildSpreadAttrsMergeCall(args) {
|
|
|
3380
3381
|
function itemAnchorTemplate(keyExpr) {
|
|
3381
3382
|
return `<!--${loopItemMarker("${" + keyExpr + "}")}-->`;
|
|
3382
3383
|
}
|
|
3384
|
+
function renderPreamble(preamble, opts) {
|
|
3385
|
+
let out = "";
|
|
3386
|
+
for (const seg of preamble.segments) {
|
|
3387
|
+
if (seg.kind === "js") {
|
|
3388
|
+
const text = opts.textVariant === "template" ? seg.templateText ?? seg.text : seg.text;
|
|
3389
|
+
out += opts.transformJs ? opts.transformJs(text) : text;
|
|
3390
|
+
} else if (opts.rawLeaf) {
|
|
3391
|
+
out += opts.renderLeaf(escapeLeafTextExpressions(seg.ir));
|
|
3392
|
+
} else {
|
|
3393
|
+
out += "`" + opts.renderLeaf(escapeLeafTextExpressions(seg.ir)) + "`";
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
return out;
|
|
3397
|
+
}
|
|
3398
|
+
function flatMapLeafKeyExpr(ir) {
|
|
3399
|
+
if (ir.type !== "element")
|
|
3400
|
+
return null;
|
|
3401
|
+
const keyAttr = ir.attrs.find((a) => a.name === "key");
|
|
3402
|
+
if (!keyAttr)
|
|
3403
|
+
return null;
|
|
3404
|
+
switch (keyAttr.value.kind) {
|
|
3405
|
+
case "expression":
|
|
3406
|
+
return `(${keyAttr.value.expr})`;
|
|
3407
|
+
case "literal":
|
|
3408
|
+
return JSON.stringify(keyAttr.value.value);
|
|
3409
|
+
case "template":
|
|
3410
|
+
return attrValueToString(keyAttr.value);
|
|
3411
|
+
default:
|
|
3412
|
+
return null;
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
function stripLeafKeyAttr(ir) {
|
|
3416
|
+
if (ir.type !== "element")
|
|
3417
|
+
return ir;
|
|
3418
|
+
return { ...ir, attrs: ir.attrs.filter((a) => a.name !== "key") };
|
|
3419
|
+
}
|
|
3420
|
+
function renderFlatMapClientBody(cb, restSpreadNames) {
|
|
3421
|
+
return renderPreamble(cb, {
|
|
3422
|
+
textVariant: "client",
|
|
3423
|
+
rawLeaf: true,
|
|
3424
|
+
renderLeaf: (ir) => {
|
|
3425
|
+
const key = flatMapLeafKeyExpr(ir);
|
|
3426
|
+
const html = irToHtmlTemplate(stripLeafKeyAttr(ir), restSpreadNames, 1, undefined, undefined, true);
|
|
3427
|
+
return `({ k: ${key ?? "undefined"}, h: \`${html}\` })`;
|
|
3428
|
+
}
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
function flatMapCallbackHasKeyedLeaf(cb) {
|
|
3432
|
+
return cb.segments.some((s) => s.kind === "jsx" && flatMapLeafKeyExpr(s.ir) !== null);
|
|
3433
|
+
}
|
|
3434
|
+
function renderFlatMapProjectionClientBody(inner, restSpreadNames) {
|
|
3435
|
+
const chained = applyLoopChain(inner);
|
|
3436
|
+
const params = inner.index ? `(${inner.param}, ${inner.index})` : `(${inner.param})`;
|
|
3437
|
+
const key = inner.key ? `(${inner.key})` : "undefined";
|
|
3438
|
+
const html = inner.children.map((c) => irToHtmlTemplate(escapeLeafTextExpressions(c), restSpreadNames, 1, undefined, undefined, true)).join("");
|
|
3439
|
+
return `${chained}.map(${params} => ({ k: ${key}, h: \`${html}\` }))`;
|
|
3440
|
+
}
|
|
3441
|
+
function escapeLeafTextExpressions(ir) {
|
|
3442
|
+
switch (ir.type) {
|
|
3443
|
+
case "element":
|
|
3444
|
+
return { ...ir, children: ir.children.map(escapeLeafTextExpressions) };
|
|
3445
|
+
case "fragment":
|
|
3446
|
+
return { ...ir, children: ir.children.map(escapeLeafTextExpressions) };
|
|
3447
|
+
case "expression": {
|
|
3448
|
+
if (ir.expr === "null" || ir.expr === "undefined")
|
|
3449
|
+
return ir;
|
|
3450
|
+
if (ir.slotId || ir.expr.trimStart().startsWith("escapeText("))
|
|
3451
|
+
return ir;
|
|
3452
|
+
return { ...ir, expr: `escapeText((${ir.expr}))`, templateExpr: ir.templateExpr ? `escapeText((${ir.templateExpr}))` : ir.templateExpr };
|
|
3453
|
+
}
|
|
3454
|
+
case "conditional":
|
|
3455
|
+
return {
|
|
3456
|
+
...ir,
|
|
3457
|
+
whenTrue: escapeLeafTextExpressions(ir.whenTrue),
|
|
3458
|
+
whenFalse: ir.whenFalse ? escapeLeafTextExpressions(ir.whenFalse) : ir.whenFalse
|
|
3459
|
+
};
|
|
3460
|
+
default:
|
|
3461
|
+
return ir;
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3383
3464
|
function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, branchSlotsVar, insideLoop = false, inHoistedChildren = false) {
|
|
3384
3465
|
const recurse = (n) => irToHtmlTemplate(n, restSpreadNames, loopDepth, loopParams, branchSlotsVar, insideLoop, inHoistedChildren);
|
|
3385
3466
|
const wrapExpr = (expr) => wrapExprWithLoopParams(expr, loopParams);
|
|
@@ -3421,15 +3502,17 @@ function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, bran
|
|
|
3421
3502
|
}
|
|
3422
3503
|
case "text":
|
|
3423
3504
|
return escapeHtml(node.value);
|
|
3424
|
-
case "expression":
|
|
3505
|
+
case "expression": {
|
|
3425
3506
|
if (node.expr === "null" || node.expr === "undefined")
|
|
3426
3507
|
return "";
|
|
3508
|
+
const inner = wrapInterpolation(wrapExpr(node.expr));
|
|
3509
|
+
const valueExpr = node.joinArrayChild ? `Array.isArray(${inner}) ? ${inner}.join('') : (${inner} ?? '')` : inner;
|
|
3427
3510
|
if (node.slotId) {
|
|
3428
|
-
const
|
|
3429
|
-
const slotted = branchSlotsVar ? inner : escapeTextSlotExpr(inner);
|
|
3511
|
+
const slotted = branchSlotsVar || node.joinArrayChild ? valueExpr : escapeTextSlotExpr(valueExpr);
|
|
3430
3512
|
return `<!--bf:${node.slotId}-->\${${slotted}}<!--/-->`;
|
|
3431
3513
|
}
|
|
3432
|
-
return `\${${
|
|
3514
|
+
return `\${${valueExpr}}`;
|
|
3515
|
+
}
|
|
3433
3516
|
case "conditional": {
|
|
3434
3517
|
const trueBranch = recurse(node.whenTrue);
|
|
3435
3518
|
const falseBranch = recurse(node.whenFalse);
|
|
@@ -3488,14 +3571,13 @@ function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, bran
|
|
|
3488
3571
|
const iterMethod = node.method ?? "map";
|
|
3489
3572
|
let mapExpr;
|
|
3490
3573
|
if (node.flatMapCallback) {
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
body = body.replace(frag.placeholder, `\`${renderedIr}\``);
|
|
3495
|
-
}
|
|
3574
|
+
const body = renderPreamble(node.flatMapCallback, {
|
|
3575
|
+
renderLeaf: (ir) => irToHtmlTemplate(stripLeafKeyAttr(ir), restSpreadNames, loopDepth + 1, loopParams, branchSlotsVar, insideLoop)
|
|
3576
|
+
});
|
|
3496
3577
|
mapExpr = `\${${wrappedArray}.flatMap(${node.flatMapCallback.params} => ${body}).join('')}`;
|
|
3497
|
-
} else if (node.
|
|
3498
|
-
|
|
3578
|
+
} else if (node.preamble) {
|
|
3579
|
+
const preamble = renderPreamble(node.preamble, { textVariant: "client", renderLeaf: (ir) => irToHtmlTemplate(ir, restSpreadNames, loopDepth + 1, loopParams, branchSlotsVar, insideLoop) });
|
|
3580
|
+
mapExpr = `\${${wrappedArray}.${iterMethod}(${callbackParam} => { ${preamble} return \`${childTemplate}\` }).join('')}`;
|
|
3499
3581
|
} else {
|
|
3500
3582
|
mapExpr = `\${${wrappedArray}.${iterMethod}(${callbackParam} => \`${childTemplate}\`).join('')}`;
|
|
3501
3583
|
}
|
|
@@ -3743,13 +3825,16 @@ function irToPlaceholderTemplate(node, restSpreadNames, loopDepth = 0, loopParam
|
|
|
3743
3825
|
}
|
|
3744
3826
|
case "text":
|
|
3745
3827
|
return escapeHtml(node.value);
|
|
3746
|
-
case "expression":
|
|
3828
|
+
case "expression": {
|
|
3747
3829
|
if (node.expr === "null" || node.expr === "undefined")
|
|
3748
3830
|
return "";
|
|
3831
|
+
const wrapped = wrapExpr(node.expr);
|
|
3832
|
+
const value = node.joinArrayChild ? `Array.isArray(${wrapped}) ? ${wrapped}.join('') : (${wrapped} ?? '')` : wrapped;
|
|
3749
3833
|
if (node.slotId) {
|
|
3750
|
-
return `<!--bf:${node.slotId}-->\${${escapeTextSlotExpr(
|
|
3834
|
+
return `<!--bf:${node.slotId}-->\${${node.joinArrayChild ? value : escapeTextSlotExpr(wrapped)}}<!--/-->`;
|
|
3751
3835
|
}
|
|
3752
|
-
return `\${${
|
|
3836
|
+
return `\${${value}}`;
|
|
3837
|
+
}
|
|
3753
3838
|
case "conditional": {
|
|
3754
3839
|
const trueBranch = recurse(node.whenTrue);
|
|
3755
3840
|
const falseBranch = recurse(node.whenFalse);
|
|
@@ -3776,14 +3861,13 @@ function irToPlaceholderTemplate(node, restSpreadNames, loopDepth = 0, loopParam
|
|
|
3776
3861
|
const iterMethod = node.method ?? "map";
|
|
3777
3862
|
let mapExpr;
|
|
3778
3863
|
if (node.flatMapCallback) {
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
body = body.replace(frag.placeholder, `\`${renderedIr}\``);
|
|
3783
|
-
}
|
|
3864
|
+
const body = renderPreamble(node.flatMapCallback, {
|
|
3865
|
+
renderLeaf: (ir) => irToPlaceholderTemplate(stripLeafKeyAttr(ir), restSpreadNames, loopDepth + 1, loopParams)
|
|
3866
|
+
});
|
|
3784
3867
|
mapExpr = `\${${wrappedArray}.flatMap(${node.flatMapCallback.params} => ${body}).join('')}`;
|
|
3785
|
-
} else if (node.
|
|
3786
|
-
|
|
3868
|
+
} else if (node.preamble) {
|
|
3869
|
+
const preamble = renderPreamble(node.preamble, { textVariant: "client", renderLeaf: (ir) => irToPlaceholderTemplate(ir, restSpreadNames, loopDepth + 1, loopParams) });
|
|
3870
|
+
mapExpr = `\${${wrappedArray}.${iterMethod}(${callbackParam} => { ${preamble} return \`${childTemplate}\` }).join('')}`;
|
|
3787
3871
|
} else {
|
|
3788
3872
|
mapExpr = `\${${wrappedArray}.${iterMethod}(${callbackParam} => \`${childTemplate}\`).join('')}`;
|
|
3789
3873
|
}
|
|
@@ -3971,13 +4055,16 @@ function irToComponentTemplateWithOpts(node, opts) {
|
|
|
3971
4055
|
}
|
|
3972
4056
|
case "text":
|
|
3973
4057
|
return escapeHtml(node.value);
|
|
3974
|
-
case "expression":
|
|
4058
|
+
case "expression": {
|
|
3975
4059
|
if (node.expr === "null" || node.expr === "undefined")
|
|
3976
4060
|
return "";
|
|
4061
|
+
const wrapped = transformExpr(node.expr, node.templateExpr);
|
|
4062
|
+
const value = node.joinArrayChild ? `Array.isArray(${wrapped}) ? ${wrapped}.join('') : (${wrapped} ?? '')` : wrapped;
|
|
3977
4063
|
if (node.slotId) {
|
|
3978
|
-
return `<!--bf:${node.slotId}-->\${${
|
|
4064
|
+
return `<!--bf:${node.slotId}-->\${${node.joinArrayChild ? value : escapeTextSlotExpr(wrapped)}}<!--/-->`;
|
|
3979
4065
|
}
|
|
3980
|
-
return `\${${
|
|
4066
|
+
return `\${${value}}`;
|
|
4067
|
+
}
|
|
3981
4068
|
case "conditional": {
|
|
3982
4069
|
if (node.clientOnly && node.slotId) {
|
|
3983
4070
|
return `<!--bf-cond-start:${node.slotId}--><!--bf-cond-end:${node.slotId}-->`;
|
|
@@ -4298,10 +4385,11 @@ function generateCsrTemplateWithOpts(node, opts) {
|
|
|
4298
4385
|
{
|
|
4299
4386
|
const transformed = transformExpr(node.expr, node.templateExpr);
|
|
4300
4387
|
const expr = transformed === UNSAFE_TEMPLATE_EXPR ? "''" : transformed;
|
|
4388
|
+
const value = node.joinArrayChild ? `Array.isArray(${expr}) ? ${expr}.join('') : (${expr} ?? '')` : expr;
|
|
4301
4389
|
if (node.slotId) {
|
|
4302
|
-
return `<!--bf:${node.slotId}-->\${${escapeTextSlotExpr(expr)}}<!--/-->`;
|
|
4390
|
+
return `<!--bf:${node.slotId}-->\${${node.joinArrayChild ? value : escapeTextSlotExpr(expr)}}<!--/-->`;
|
|
4303
4391
|
}
|
|
4304
|
-
return `\${${
|
|
4392
|
+
return `\${${value}}`;
|
|
4305
4393
|
}
|
|
4306
4394
|
case "conditional": {
|
|
4307
4395
|
if (node.clientOnly && node.slotId) {
|
|
@@ -4397,16 +4485,14 @@ function generateCsrTemplateWithOpts(node, opts) {
|
|
|
4397
4485
|
const iterMethod = node.method ?? "map";
|
|
4398
4486
|
let mapExpr;
|
|
4399
4487
|
if (node.flatMapCallback) {
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
}
|
|
4405
|
-
body = applyPropsRewrite(body, propsObjectName ?? null);
|
|
4488
|
+
const body = renderPreamble(node.flatMapCallback, {
|
|
4489
|
+
textVariant: "template",
|
|
4490
|
+
transformJs: (t) => applyPropsRewrite(t, propsObjectName ?? null),
|
|
4491
|
+
renderLeaf: (ir) => recurseInLoopBody(stripLeafKeyAttr(ir))
|
|
4492
|
+
});
|
|
4406
4493
|
mapExpr = `\${${iterArrayExpr}.flatMap(${node.flatMapCallback.params} => ${body}).join('')}`;
|
|
4407
|
-
} else if (node.
|
|
4408
|
-
const
|
|
4409
|
-
const preamble = applyPropsRewrite(rawPreamble, propsObjectName ?? null);
|
|
4494
|
+
} else if (node.preamble) {
|
|
4495
|
+
const preamble = renderPreamble(node.preamble, { textVariant: "template", transformJs: (t) => applyPropsRewrite(t, propsObjectName ?? null), renderLeaf: (ir) => recurseInLoopBody(ir) });
|
|
4410
4496
|
mapExpr = `\${${iterArrayExpr}.${iterMethod}(${callbackParam} => { ${preamble} return \`${childTemplate}\` }).join('')}`;
|
|
4411
4497
|
} else {
|
|
4412
4498
|
mapExpr = `\${${iterArrayExpr}.${iterMethod}(${callbackParam} => \`${childTemplate}\`).join('')}`;
|
|
@@ -4561,6 +4647,39 @@ function reconstructWithoutTypes(node, sourceFile, ranges) {
|
|
|
4561
4647
|
}
|
|
4562
4648
|
return result;
|
|
4563
4649
|
}
|
|
4650
|
+
function reconstructAsSegments(node, sourceFile, ranges, markers) {
|
|
4651
|
+
const nodeStart = node.getStart(sourceFile);
|
|
4652
|
+
const nodeEnd = node.getEnd();
|
|
4653
|
+
const fullText = sourceFile.text;
|
|
4654
|
+
const edits = [];
|
|
4655
|
+
for (const r of ranges) {
|
|
4656
|
+
if (r.end <= nodeStart || r.start >= nodeEnd)
|
|
4657
|
+
continue;
|
|
4658
|
+
edits.push({ start: r.start, end: r.end, marker: null });
|
|
4659
|
+
}
|
|
4660
|
+
markers.forEach((m, i) => edits.push({ start: m.start, end: m.end, marker: i }));
|
|
4661
|
+
edits.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
4662
|
+
const segments = [];
|
|
4663
|
+
let jsBuf = "";
|
|
4664
|
+
let pos = nodeStart;
|
|
4665
|
+
for (const edit of edits) {
|
|
4666
|
+
if (edit.start < pos)
|
|
4667
|
+
continue;
|
|
4668
|
+
jsBuf += fullText.slice(pos, edit.start);
|
|
4669
|
+
if (edit.marker !== null) {
|
|
4670
|
+
if (jsBuf)
|
|
4671
|
+
segments.push({ js: jsBuf });
|
|
4672
|
+
jsBuf = "";
|
|
4673
|
+
segments.push({ marker: edit.marker });
|
|
4674
|
+
}
|
|
4675
|
+
pos = edit.end;
|
|
4676
|
+
}
|
|
4677
|
+
if (pos < nodeEnd)
|
|
4678
|
+
jsBuf += fullText.slice(pos, nodeEnd);
|
|
4679
|
+
if (jsBuf)
|
|
4680
|
+
segments.push({ js: jsBuf });
|
|
4681
|
+
return segments;
|
|
4682
|
+
}
|
|
4564
4683
|
function mergeRanges(ranges) {
|
|
4565
4684
|
if (ranges.length === 0)
|
|
4566
4685
|
return [];
|
|
@@ -4706,10 +4825,11 @@ function findAngleBracketAfter(lastTypeArg, fullText) {
|
|
|
4706
4825
|
}
|
|
4707
4826
|
|
|
4708
4827
|
// src/analyzer-context.ts
|
|
4709
|
-
function createAnalyzerContext(sourceFile, filePath) {
|
|
4828
|
+
function createAnalyzerContext(sourceFile, filePath, acceptsCallbackBody) {
|
|
4710
4829
|
return {
|
|
4711
4830
|
sourceFile,
|
|
4712
4831
|
filePath,
|
|
4832
|
+
acceptsCallbackBody,
|
|
4713
4833
|
componentName: null,
|
|
4714
4834
|
componentNode: null,
|
|
4715
4835
|
hasDefaultExport: false,
|
|
@@ -4755,6 +4875,9 @@ function createAnalyzerContext(sourceFile, filePath) {
|
|
|
4755
4875
|
} catch {
|
|
4756
4876
|
ownSourceFile = undefined;
|
|
4757
4877
|
}
|
|
4878
|
+
if (process.env.BF_ASSERT_NO_JSX_IN_GETJS === "1" && this.errors.length === 0 && nodeContainsJsx(node)) {
|
|
4879
|
+
throw new Error("getJS() called on a JSX-bearing node — raw JSX must never be spliced " + "into emitted output. Carry mixed content as structured segments " + "(MapCallbackPreamble / FlatMapCallback) instead.");
|
|
4880
|
+
}
|
|
4758
4881
|
if (ownSourceFile && ownSourceFile !== sourceFile) {
|
|
4759
4882
|
return node.getText(ownSourceFile);
|
|
4760
4883
|
}
|
|
@@ -4762,6 +4885,11 @@ function createAnalyzerContext(sourceFile, filePath) {
|
|
|
4762
4885
|
}
|
|
4763
4886
|
};
|
|
4764
4887
|
}
|
|
4888
|
+
function nodeContainsJsx(node) {
|
|
4889
|
+
if (ts7.isJsxElement(node) || ts7.isJsxSelfClosingElement(node) || ts7.isJsxFragment(node))
|
|
4890
|
+
return true;
|
|
4891
|
+
return ts7.forEachChild(node, nodeContainsJsx) ?? false;
|
|
4892
|
+
}
|
|
4765
4893
|
function getSourceLocation(node, sourceFile, filePath) {
|
|
4766
4894
|
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
4767
4895
|
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
@@ -5564,7 +5692,7 @@ function createProgramForFile(source, filePath) {
|
|
|
5564
5692
|
return null;
|
|
5565
5693
|
}
|
|
5566
5694
|
}
|
|
5567
|
-
function analyzeComponent(source, filePath, targetComponentName, program) {
|
|
5695
|
+
function analyzeComponent(source, filePath, targetComponentName, program, acceptsCallbackBody) {
|
|
5568
5696
|
incrementCounter("filesAnalyzed");
|
|
5569
5697
|
const hadSharedProgram = program !== undefined;
|
|
5570
5698
|
const prescan = prescanReactiveFactoriesInSource(source, filePath);
|
|
@@ -5597,7 +5725,7 @@ function analyzeComponent(source, filePath, targetComponentName, program) {
|
|
|
5597
5725
|
checker = result.checker;
|
|
5598
5726
|
}
|
|
5599
5727
|
}
|
|
5600
|
-
const ctx = createAnalyzerContext(sourceFile, filePath);
|
|
5728
|
+
const ctx = createAnalyzerContext(sourceFile, filePath, acceptsCallbackBody);
|
|
5601
5729
|
ctx.checker = checker;
|
|
5602
5730
|
ctx.reactiveFactories = prescan.factories;
|
|
5603
5731
|
ctx.declinedReactiveFactories = prescan.declined;
|
|
@@ -6649,9 +6777,15 @@ function extractSingleJsxReturn(body) {
|
|
|
6649
6777
|
return null;
|
|
6650
6778
|
return jsxReturn;
|
|
6651
6779
|
}
|
|
6652
|
-
function
|
|
6780
|
+
function preambleUnsafe(preamble, branches, fallback) {
|
|
6781
|
+
if (preamble.length === 0)
|
|
6782
|
+
return false;
|
|
6783
|
+
return fallback === null || branches.some((b) => b.jsxReturn === null);
|
|
6784
|
+
}
|
|
6785
|
+
function extractMultiReturnJsxBranches(body, allowPreamble = false) {
|
|
6653
6786
|
const branches = [];
|
|
6654
6787
|
let fallback = null;
|
|
6788
|
+
const preamble = [];
|
|
6655
6789
|
const stmts = body.statements;
|
|
6656
6790
|
for (let i = 0;i < stmts.length; i++) {
|
|
6657
6791
|
const stmt = stmts[i];
|
|
@@ -6681,7 +6815,9 @@ function extractMultiReturnJsxBranches(body) {
|
|
|
6681
6815
|
}
|
|
6682
6816
|
if (branches.length === 0)
|
|
6683
6817
|
return null;
|
|
6684
|
-
|
|
6818
|
+
if (preambleUnsafe(preamble, branches, fallback))
|
|
6819
|
+
return null;
|
|
6820
|
+
return { branches, fallback, preamble };
|
|
6685
6821
|
}
|
|
6686
6822
|
break;
|
|
6687
6823
|
}
|
|
@@ -6696,23 +6832,38 @@ function extractMultiReturnJsxBranches(body) {
|
|
|
6696
6832
|
const hasDefault = stmt.caseBlock.clauses.some((c) => ts8.isDefaultClause(c));
|
|
6697
6833
|
if (!hasDefault)
|
|
6698
6834
|
return null;
|
|
6835
|
+
let pendingCases = [];
|
|
6699
6836
|
for (const clause of stmt.caseBlock.clauses) {
|
|
6837
|
+
if (ts8.isCaseClause(clause) && clause.statements.length === 0) {
|
|
6838
|
+
pendingCases.push(clause.expression);
|
|
6839
|
+
continue;
|
|
6840
|
+
}
|
|
6700
6841
|
const jsxReturn = findJsxReturnInCaseClause(clause);
|
|
6701
6842
|
const nullReturn = findNullReturnInCaseClause(clause);
|
|
6702
6843
|
if (!jsxReturn && !nullReturn)
|
|
6703
6844
|
return null;
|
|
6845
|
+
if (!caseClauseIsDirectReturn(clause))
|
|
6846
|
+
return null;
|
|
6704
6847
|
if (ts8.isCaseClause(clause)) {
|
|
6705
6848
|
branches.push({
|
|
6706
6849
|
condition: clause.expression,
|
|
6707
|
-
jsxReturn: jsxReturn ?? null
|
|
6850
|
+
jsxReturn: jsxReturn ?? null,
|
|
6851
|
+
extraCaseConditions: pendingCases.length > 0 ? pendingCases : undefined
|
|
6708
6852
|
});
|
|
6853
|
+
pendingCases = [];
|
|
6709
6854
|
} else {
|
|
6855
|
+
if (pendingCases.length > 0)
|
|
6856
|
+
return null;
|
|
6710
6857
|
fallback = jsxReturn ?? null;
|
|
6711
6858
|
}
|
|
6712
6859
|
}
|
|
6860
|
+
if (pendingCases.length > 0)
|
|
6861
|
+
return null;
|
|
6713
6862
|
if (branches.length === 0)
|
|
6714
6863
|
return null;
|
|
6715
|
-
|
|
6864
|
+
if (preambleUnsafe(preamble, branches, fallback))
|
|
6865
|
+
return null;
|
|
6866
|
+
return { branches, fallback, switchDiscriminant: stmt.expression, preamble };
|
|
6716
6867
|
}
|
|
6717
6868
|
if (ts8.isReturnStatement(stmt) && stmt.expression) {
|
|
6718
6869
|
const expr = unwrapJsxTransparent(stmt.expression);
|
|
@@ -6723,13 +6874,22 @@ function extractMultiReturnJsxBranches(body) {
|
|
|
6723
6874
|
}
|
|
6724
6875
|
continue;
|
|
6725
6876
|
}
|
|
6726
|
-
if (ts8.isVariableStatement(stmt))
|
|
6877
|
+
if (ts8.isVariableStatement(stmt)) {
|
|
6878
|
+
const declFlags = stmt.declarationList.flags;
|
|
6879
|
+
const isConstOrLet = (declFlags & ts8.NodeFlags.Const) !== 0 || (declFlags & ts8.NodeFlags.Let) !== 0;
|
|
6880
|
+
if (allowPreamble && isConstOrLet && branches.length === 0 && fallback === null) {
|
|
6881
|
+
preamble.push(stmt);
|
|
6882
|
+
continue;
|
|
6883
|
+
}
|
|
6727
6884
|
return null;
|
|
6885
|
+
}
|
|
6728
6886
|
return null;
|
|
6729
6887
|
}
|
|
6730
6888
|
if (branches.length === 0)
|
|
6731
6889
|
return null;
|
|
6732
|
-
|
|
6890
|
+
if (preambleUnsafe(preamble, branches, fallback))
|
|
6891
|
+
return null;
|
|
6892
|
+
return { branches, fallback, preamble };
|
|
6733
6893
|
}
|
|
6734
6894
|
function isDirectReturnBlock(node) {
|
|
6735
6895
|
if (ts8.isReturnStatement(node))
|
|
@@ -6739,9 +6899,9 @@ function isDirectReturnBlock(node) {
|
|
|
6739
6899
|
for (const stmt of node.statements) {
|
|
6740
6900
|
if (ts8.isReturnStatement(stmt)) {
|
|
6741
6901
|
returnCount++;
|
|
6742
|
-
|
|
6743
|
-
return false;
|
|
6902
|
+
continue;
|
|
6744
6903
|
}
|
|
6904
|
+
return false;
|
|
6745
6905
|
}
|
|
6746
6906
|
return returnCount === 1;
|
|
6747
6907
|
}
|
|
@@ -6772,6 +6932,24 @@ function findJsxReturnInCaseClause(clause) {
|
|
|
6772
6932
|
}
|
|
6773
6933
|
return null;
|
|
6774
6934
|
}
|
|
6935
|
+
function caseClauseIsDirectReturn(clause) {
|
|
6936
|
+
let returnCount = 0;
|
|
6937
|
+
let seenReturn = false;
|
|
6938
|
+
for (const stmt of clause.statements) {
|
|
6939
|
+
if (ts8.isReturnStatement(stmt)) {
|
|
6940
|
+
returnCount++;
|
|
6941
|
+
seenReturn = true;
|
|
6942
|
+
continue;
|
|
6943
|
+
}
|
|
6944
|
+
if (ts8.isBreakStatement(stmt)) {
|
|
6945
|
+
if (!seenReturn)
|
|
6946
|
+
return false;
|
|
6947
|
+
continue;
|
|
6948
|
+
}
|
|
6949
|
+
return false;
|
|
6950
|
+
}
|
|
6951
|
+
return returnCount === 1;
|
|
6952
|
+
}
|
|
6775
6953
|
function findNullReturnInCaseClause(clause) {
|
|
6776
6954
|
for (const stmt of clause.statements) {
|
|
6777
6955
|
if (ts8.isReturnStatement(stmt) && stmt.expression) {
|
|
@@ -8812,6 +8990,23 @@ var REACTIVE_BINDING_KINDS = new Set([
|
|
|
8812
8990
|
function isReactiveOrigin(origin) {
|
|
8813
8991
|
return origin.freeRefs?.some((r) => REACTIVE_BINDING_KINDS.has(r.kind)) ?? false;
|
|
8814
8992
|
}
|
|
8993
|
+
function tsxSourceText(raw) {
|
|
8994
|
+
return raw;
|
|
8995
|
+
}
|
|
8996
|
+
function preambleAnalysisText(p) {
|
|
8997
|
+
let out = "";
|
|
8998
|
+
for (const seg of p.segments)
|
|
8999
|
+
if (seg.kind === "js")
|
|
9000
|
+
out += seg.text;
|
|
9001
|
+
return out;
|
|
9002
|
+
}
|
|
9003
|
+
function preambleAnalysisTemplateText(p) {
|
|
9004
|
+
let out = "";
|
|
9005
|
+
for (const seg of p.segments)
|
|
9006
|
+
if (seg.kind === "js")
|
|
9007
|
+
out += seg.templateText ?? seg.text;
|
|
9008
|
+
return out;
|
|
9009
|
+
}
|
|
8815
9010
|
var AttrValueOf = {
|
|
8816
9011
|
literal(value) {
|
|
8817
9012
|
return { kind: "literal", value };
|
|
@@ -10051,8 +10246,13 @@ function attachParsedExpressions(node, analyzer, bound = EMPTY_BOUND) {
|
|
|
10051
10246
|
for (const child of nested.children)
|
|
10052
10247
|
attachParsedExpressions(child, analyzer, loopBound);
|
|
10053
10248
|
}
|
|
10054
|
-
for (const
|
|
10055
|
-
|
|
10249
|
+
for (const seg of node.flatMapCallback?.segments ?? []) {
|
|
10250
|
+
if (seg.kind === "jsx")
|
|
10251
|
+
attachParsedExpressions(seg.ir, analyzer, loopBound);
|
|
10252
|
+
}
|
|
10253
|
+
for (const seg of node.preamble?.segments ?? []) {
|
|
10254
|
+
if (seg.kind === "jsx")
|
|
10255
|
+
attachParsedExpressions(seg.ir, analyzer, loopBound);
|
|
10056
10256
|
}
|
|
10057
10257
|
break;
|
|
10058
10258
|
}
|
|
@@ -10687,74 +10887,78 @@ function transformMultiReturnJsxFunctionCall(callExpr, info, ctx) {
|
|
|
10687
10887
|
ctx.analyzer.getJS = substitutedGetJS;
|
|
10688
10888
|
try {
|
|
10689
10889
|
const loc = getSourceLocation(callExpr, ctx.sourceFile, ctx.filePath);
|
|
10690
|
-
|
|
10691
|
-
type: "expression",
|
|
10692
|
-
expr: "null",
|
|
10693
|
-
typeInfo: { kind: "primitive", raw: "null", primitive: "null" },
|
|
10694
|
-
reactive: false,
|
|
10695
|
-
slotId: null,
|
|
10696
|
-
loc,
|
|
10697
|
-
origin: { phase: "tick", scope: "template", effect: "pure", freeRefs: [] }
|
|
10698
|
-
};
|
|
10699
|
-
let result = info.fallback ? transformNode(info.fallback, ctx) ?? nullExpr : nullExpr;
|
|
10700
|
-
for (let i = info.branches.length - 1;i >= 0; i--) {
|
|
10701
|
-
const branch = info.branches[i];
|
|
10702
|
-
let conditionText;
|
|
10703
|
-
if (info.switchDiscriminant) {
|
|
10704
|
-
const discText = substitutedGetJS(info.switchDiscriminant);
|
|
10705
|
-
const caseText = substitutedGetJS(branch.condition);
|
|
10706
|
-
conditionText = `${discText} === ${caseText}`;
|
|
10707
|
-
} else {
|
|
10708
|
-
conditionText = substitutedGetJS(branch.condition);
|
|
10709
|
-
}
|
|
10710
|
-
const env = makeBindingEnv(ctx);
|
|
10711
|
-
const caseFreeRefs = resolveFreeRefs(branch.condition, env);
|
|
10712
|
-
const discFreeRefs = info.switchDiscriminant ? resolveFreeRefs(info.switchDiscriminant, env) : [];
|
|
10713
|
-
const conditionOrigin = {
|
|
10714
|
-
phase: "tick",
|
|
10715
|
-
scope: "template",
|
|
10716
|
-
effect: "pure",
|
|
10717
|
-
freeRefs: [...discFreeRefs, ...caseFreeRefs]
|
|
10718
|
-
};
|
|
10719
|
-
const reactive = isReactiveExpression(conditionText, ctx, branch.condition) || isReactiveOrigin(conditionOrigin);
|
|
10720
|
-
const loopParamReactive = !reactive && referencesLoopParam(conditionText, ctx);
|
|
10721
|
-
const callsReactive = exprCallsReactiveGetters(branch.condition, ctx) || (info.switchDiscriminant ? exprCallsReactiveGetters(info.switchDiscriminant, ctx) : false);
|
|
10722
|
-
const hasCalls = exprHasFunctionCalls(branch.condition) || (info.switchDiscriminant ? exprHasFunctionCalls(info.switchDiscriminant) : false);
|
|
10723
|
-
const needsSlot = reactive || loopParamReactive || callsReactive || hasCalls;
|
|
10724
|
-
const slotId = needsSlot ? generateSlotId(ctx) : null;
|
|
10725
|
-
const whenTrue = branch.jsxReturn ? transformNode(branch.jsxReturn, ctx) ?? nullExpr : nullExpr;
|
|
10726
|
-
let templateCondition;
|
|
10727
|
-
if (info.switchDiscriminant) {
|
|
10728
|
-
const discRewritten = rewriteBarePropRefs2(substitutedGetJS(info.switchDiscriminant), info.switchDiscriminant, ctx);
|
|
10729
|
-
const caseRewritten = rewriteBarePropRefs2(substitutedGetJS(branch.condition), branch.condition, ctx);
|
|
10730
|
-
const discPart = discRewritten ?? substitutedGetJS(info.switchDiscriminant);
|
|
10731
|
-
const casePart = caseRewritten ?? substitutedGetJS(branch.condition);
|
|
10732
|
-
templateCondition = `${discPart} === ${casePart}`;
|
|
10733
|
-
} else {
|
|
10734
|
-
templateCondition = rewriteBarePropRefs2(conditionText, branch.condition, ctx);
|
|
10735
|
-
}
|
|
10736
|
-
const conditional = {
|
|
10737
|
-
type: "conditional",
|
|
10738
|
-
condition: conditionText,
|
|
10739
|
-
templateCondition,
|
|
10740
|
-
conditionType: null,
|
|
10741
|
-
reactive,
|
|
10742
|
-
whenTrue,
|
|
10743
|
-
whenFalse: result,
|
|
10744
|
-
slotId,
|
|
10745
|
-
callsReactiveGetters: callsReactive || undefined,
|
|
10746
|
-
hasFunctionCalls: hasCalls || undefined,
|
|
10747
|
-
loc,
|
|
10748
|
-
origin: conditionOrigin
|
|
10749
|
-
};
|
|
10750
|
-
result = conditional;
|
|
10751
|
-
}
|
|
10752
|
-
return result;
|
|
10890
|
+
return foldMultiReturnBranches(info, ctx, loc, substitutedGetJS);
|
|
10753
10891
|
} finally {
|
|
10754
10892
|
ctx.getJS = originalCtxGetJS;
|
|
10755
10893
|
ctx.analyzer.getJS = originalAnalyzerGetJS;
|
|
10756
10894
|
}
|
|
10757
10895
|
}
|
|
10896
|
+
function foldMultiReturnBranches(info, ctx, loc, getText) {
|
|
10897
|
+
const nullExpr = {
|
|
10898
|
+
type: "expression",
|
|
10899
|
+
expr: "null",
|
|
10900
|
+
typeInfo: { kind: "primitive", raw: "null", primitive: "null" },
|
|
10901
|
+
reactive: false,
|
|
10902
|
+
slotId: null,
|
|
10903
|
+
loc,
|
|
10904
|
+
origin: { phase: "tick", scope: "template", effect: "pure", freeRefs: [] }
|
|
10905
|
+
};
|
|
10906
|
+
let result = info.fallback ? transformNode(info.fallback, ctx) ?? nullExpr : nullExpr;
|
|
10907
|
+
for (let i = info.branches.length - 1;i >= 0; i--) {
|
|
10908
|
+
const branch = info.branches[i];
|
|
10909
|
+
const caseConds = info.switchDiscriminant ? [branch.condition, ...branch.extraCaseConditions ?? []] : [branch.condition];
|
|
10910
|
+
let conditionText;
|
|
10911
|
+
if (info.switchDiscriminant) {
|
|
10912
|
+
const discText = getText(info.switchDiscriminant);
|
|
10913
|
+
conditionText = caseConds.map((c) => `(${discText}) === (${getText(c)})`).join(" || ");
|
|
10914
|
+
} else {
|
|
10915
|
+
conditionText = getText(branch.condition);
|
|
10916
|
+
}
|
|
10917
|
+
const env = makeBindingEnv(ctx);
|
|
10918
|
+
const caseFreeRefs = caseConds.flatMap((c) => resolveFreeRefs(c, env));
|
|
10919
|
+
const discFreeRefs = info.switchDiscriminant ? resolveFreeRefs(info.switchDiscriminant, env) : [];
|
|
10920
|
+
const conditionOrigin = {
|
|
10921
|
+
phase: "tick",
|
|
10922
|
+
scope: "template",
|
|
10923
|
+
effect: "pure",
|
|
10924
|
+
freeRefs: [...discFreeRefs, ...caseFreeRefs]
|
|
10925
|
+
};
|
|
10926
|
+
const reactive = isReactiveExpression(conditionText, ctx, branch.condition) || isReactiveOrigin(conditionOrigin);
|
|
10927
|
+
const loopParamReactive = !reactive && referencesLoopParam(conditionText, ctx);
|
|
10928
|
+
const callsReactive = caseConds.some((c) => exprCallsReactiveGetters(c, ctx)) || (info.switchDiscriminant ? exprCallsReactiveGetters(info.switchDiscriminant, ctx) : false);
|
|
10929
|
+
const hasCalls = caseConds.some((c) => exprHasFunctionCalls(c)) || (info.switchDiscriminant ? exprHasFunctionCalls(info.switchDiscriminant) : false);
|
|
10930
|
+
const needsSlot = reactive || loopParamReactive || callsReactive || hasCalls;
|
|
10931
|
+
const slotId = needsSlot ? generateSlotId(ctx) : null;
|
|
10932
|
+
const whenTrue = branch.jsxReturn ? transformNode(branch.jsxReturn, ctx) ?? nullExpr : nullExpr;
|
|
10933
|
+
let templateCondition;
|
|
10934
|
+
if (info.switchDiscriminant) {
|
|
10935
|
+
const discRewritten = rewriteBarePropRefs2(getText(info.switchDiscriminant), info.switchDiscriminant, ctx);
|
|
10936
|
+
const discPart = discRewritten ?? getText(info.switchDiscriminant);
|
|
10937
|
+
templateCondition = caseConds.map((c) => {
|
|
10938
|
+
const casePart = rewriteBarePropRefs2(getText(c), c, ctx) ?? getText(c);
|
|
10939
|
+
return `(${discPart}) === (${casePart})`;
|
|
10940
|
+
}).join(" || ");
|
|
10941
|
+
} else {
|
|
10942
|
+
templateCondition = rewriteBarePropRefs2(conditionText, branch.condition, ctx);
|
|
10943
|
+
}
|
|
10944
|
+
const conditional = {
|
|
10945
|
+
type: "conditional",
|
|
10946
|
+
condition: conditionText,
|
|
10947
|
+
templateCondition,
|
|
10948
|
+
conditionType: null,
|
|
10949
|
+
reactive,
|
|
10950
|
+
whenTrue,
|
|
10951
|
+
whenFalse: result,
|
|
10952
|
+
slotId,
|
|
10953
|
+
callsReactiveGetters: callsReactive || undefined,
|
|
10954
|
+
hasFunctionCalls: hasCalls || undefined,
|
|
10955
|
+
loc,
|
|
10956
|
+
origin: conditionOrigin
|
|
10957
|
+
};
|
|
10958
|
+
result = conditional;
|
|
10959
|
+
}
|
|
10960
|
+
return result;
|
|
10961
|
+
}
|
|
10758
10962
|
function transformConditional(node, ctx) {
|
|
10759
10963
|
const condition = ctx.getJS(node.condition);
|
|
10760
10964
|
const conditionOrigin = {
|
|
@@ -11551,6 +11755,87 @@ function checkLoopKey(callback, ctx, isNested) {
|
|
|
11551
11755
|
return;
|
|
11552
11756
|
}
|
|
11553
11757
|
}
|
|
11758
|
+
function flatMapProjectionCall(body) {
|
|
11759
|
+
let expr;
|
|
11760
|
+
if (ts11.isBlock(body)) {
|
|
11761
|
+
const real = body.statements;
|
|
11762
|
+
if (real.length !== 1 || !ts11.isReturnStatement(real[0]) || !real[0].expression)
|
|
11763
|
+
return null;
|
|
11764
|
+
expr = real[0].expression;
|
|
11765
|
+
} else {
|
|
11766
|
+
expr = body;
|
|
11767
|
+
}
|
|
11768
|
+
while (ts11.isParenthesizedExpression(expr))
|
|
11769
|
+
expr = expr.expression;
|
|
11770
|
+
if (!ts11.isCallExpression(expr))
|
|
11771
|
+
return null;
|
|
11772
|
+
if (!getMapLikeMethod(expr))
|
|
11773
|
+
return null;
|
|
11774
|
+
const cb = expr.arguments[0];
|
|
11775
|
+
if (!cb || !ts11.isArrowFunction(cb) && !ts11.isFunctionExpression(cb))
|
|
11776
|
+
return null;
|
|
11777
|
+
for (const p of cb.parameters) {
|
|
11778
|
+
if (!ts11.isIdentifier(p.name))
|
|
11779
|
+
return null;
|
|
11780
|
+
}
|
|
11781
|
+
let innerBody = cb.body;
|
|
11782
|
+
if (ts11.isBlock(innerBody)) {
|
|
11783
|
+
const ret = innerBody.statements.find((s) => ts11.isReturnStatement(s) && s.expression != null);
|
|
11784
|
+
if (innerBody.statements.length !== 1 || !ret?.expression)
|
|
11785
|
+
return null;
|
|
11786
|
+
innerBody = ret.expression;
|
|
11787
|
+
}
|
|
11788
|
+
while (ts11.isParenthesizedExpression(innerBody))
|
|
11789
|
+
innerBody = innerBody.expression;
|
|
11790
|
+
const isElementish = (n) => {
|
|
11791
|
+
let m = n;
|
|
11792
|
+
while (ts11.isParenthesizedExpression(m))
|
|
11793
|
+
m = m.expression;
|
|
11794
|
+
if (ts11.isJsxElement(m) || ts11.isJsxSelfClosingElement(m))
|
|
11795
|
+
return leafIsWirelessElement(m);
|
|
11796
|
+
if (ts11.isConditionalExpression(m))
|
|
11797
|
+
return isElementish(m.whenTrue) && isElementish(m.whenFalse);
|
|
11798
|
+
return false;
|
|
11799
|
+
};
|
|
11800
|
+
if (!isElementish(innerBody))
|
|
11801
|
+
return null;
|
|
11802
|
+
return expr;
|
|
11803
|
+
}
|
|
11804
|
+
function leafIsWirelessElement(el) {
|
|
11805
|
+
let ok = true;
|
|
11806
|
+
const visit2 = (n) => {
|
|
11807
|
+
if (!ok)
|
|
11808
|
+
return;
|
|
11809
|
+
if (ts11.isJsxOpeningElement(n) || ts11.isJsxSelfClosingElement(n)) {
|
|
11810
|
+
const tagNode = n.tagName;
|
|
11811
|
+
const isIntrinsic = ts11.isIdentifier(tagNode) ? !/^[A-Z]/.test(tagNode.text) : ts11.isJsxNamespacedName(tagNode);
|
|
11812
|
+
if (!isIntrinsic) {
|
|
11813
|
+
ok = false;
|
|
11814
|
+
return;
|
|
11815
|
+
}
|
|
11816
|
+
for (const attr of n.attributes.properties) {
|
|
11817
|
+
if (ts11.isJsxSpreadAttribute(attr)) {
|
|
11818
|
+
ok = false;
|
|
11819
|
+
return;
|
|
11820
|
+
}
|
|
11821
|
+
if (ts11.isJsxAttribute(attr)) {
|
|
11822
|
+
const name = attr.name.getText();
|
|
11823
|
+
if (/^on[A-Z]/.test(name)) {
|
|
11824
|
+
ok = false;
|
|
11825
|
+
return;
|
|
11826
|
+
}
|
|
11827
|
+
}
|
|
11828
|
+
}
|
|
11829
|
+
}
|
|
11830
|
+
if (ts11.isCallExpression(n) && getMapLikeMethod(n) && containsJsxInExpression(n)) {
|
|
11831
|
+
ok = false;
|
|
11832
|
+
return;
|
|
11833
|
+
}
|
|
11834
|
+
ts11.forEachChild(n, visit2);
|
|
11835
|
+
};
|
|
11836
|
+
visit2(el);
|
|
11837
|
+
return ok;
|
|
11838
|
+
}
|
|
11554
11839
|
function loopBodyIsMultiRoot(children) {
|
|
11555
11840
|
const real = children.filter((c) => !(c.type === "text" && typeof c.value === "string" && !c.value.trim()));
|
|
11556
11841
|
if (real.length === 0)
|
|
@@ -11596,6 +11881,7 @@ function extractItemConditionalKey(cond) {
|
|
|
11596
11881
|
}
|
|
11597
11882
|
function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
|
|
11598
11883
|
const isNested = ctx.loopParams.size > 0;
|
|
11884
|
+
const diagCountAtEntry = ctx.analyzer.errors.length;
|
|
11599
11885
|
const depth = ctx.loopDepth;
|
|
11600
11886
|
const propAccess = node.expression;
|
|
11601
11887
|
const mapSource = propAccess.expression;
|
|
@@ -11605,9 +11891,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
|
|
|
11605
11891
|
let filterPredicate;
|
|
11606
11892
|
let sortComparator;
|
|
11607
11893
|
let chainOrder;
|
|
11608
|
-
let
|
|
11609
|
-
let templateMapPreamble;
|
|
11610
|
-
let typedMapPreamble;
|
|
11894
|
+
let preamble;
|
|
11611
11895
|
let iterationShape;
|
|
11612
11896
|
let objectIteration;
|
|
11613
11897
|
const setArray = (node2) => {
|
|
@@ -11637,7 +11921,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
|
|
|
11637
11921
|
const innerFilter = isFilterCall(sortInfo.array);
|
|
11638
11922
|
const sortExtraction = extractSortComparator(sortInfo.callback, sortInfo.method, ctx);
|
|
11639
11923
|
if (isClientOnly || !sortExtraction.result) {
|
|
11640
|
-
if (!isClientOnly && sortExtraction.unsupportedReason) {
|
|
11924
|
+
if (!isClientOnly && sortExtraction.unsupportedReason && !(ctx.analyzer.acceptsCallbackBody?.("sort") ?? false)) {
|
|
11641
11925
|
ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(sortInfo.callback, ctx.sourceFile, ctx.filePath), {
|
|
11642
11926
|
message: `Expression cannot be compiled to marked template: ${sortExtraction.unsupportedReason}`,
|
|
11643
11927
|
suggestion: {
|
|
@@ -11652,7 +11936,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
|
|
|
11652
11936
|
chainOrder = "filter-sort";
|
|
11653
11937
|
const filterExtraction = extractFilterPredicate(innerFilter.callback, ctx);
|
|
11654
11938
|
if (isClientOnly || !filterExtraction.result) {
|
|
11655
|
-
if (!isClientOnly && filterExtraction.unsupportedReason) {
|
|
11939
|
+
if (!isClientOnly && filterExtraction.unsupportedReason && !(ctx.analyzer.acceptsCallbackBody?.("filter") ?? false)) {
|
|
11656
11940
|
ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(innerFilter.callback, ctx.sourceFile, ctx.filePath), {
|
|
11657
11941
|
message: `Expression cannot be compiled to marked template: ${filterExtraction.unsupportedReason}`,
|
|
11658
11942
|
suggestion: {
|
|
@@ -11675,7 +11959,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
|
|
|
11675
11959
|
const innerSort = isSortCall(filterInfo.array);
|
|
11676
11960
|
const filterExtraction = extractFilterPredicate(filterInfo.callback, ctx);
|
|
11677
11961
|
if (isClientOnly || !filterExtraction.result) {
|
|
11678
|
-
if (!isClientOnly && filterExtraction.unsupportedReason) {
|
|
11962
|
+
if (!isClientOnly && filterExtraction.unsupportedReason && !(ctx.analyzer.acceptsCallbackBody?.("filter") ?? false)) {
|
|
11679
11963
|
ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(filterInfo.callback, ctx.sourceFile, ctx.filePath), {
|
|
11680
11964
|
message: `Expression cannot be compiled to marked template: ${filterExtraction.unsupportedReason}`,
|
|
11681
11965
|
suggestion: {
|
|
@@ -11690,7 +11974,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
|
|
|
11690
11974
|
chainOrder = "sort-filter";
|
|
11691
11975
|
const sortExtraction = extractSortComparator(innerSort.callback, innerSort.method, ctx);
|
|
11692
11976
|
if (isClientOnly || !sortExtraction.result) {
|
|
11693
|
-
if (!isClientOnly && sortExtraction.unsupportedReason) {
|
|
11977
|
+
if (!isClientOnly && sortExtraction.unsupportedReason && !(ctx.analyzer.acceptsCallbackBody?.("sort") ?? false)) {
|
|
11694
11978
|
ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(innerSort.callback, ctx.sourceFile, ctx.filePath), {
|
|
11695
11979
|
message: `Expression cannot be compiled to marked template: ${sortExtraction.unsupportedReason}`,
|
|
11696
11980
|
suggestion: {
|
|
@@ -11805,7 +12089,24 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
|
|
|
11805
12089
|
} else if (method === "flatMap" && ts11.isArrayLiteralExpression(body)) {
|
|
11806
12090
|
children = transformArrayLiteralChildren(body, ctx);
|
|
11807
12091
|
} else if (ts11.isBlock(body)) {
|
|
11808
|
-
const
|
|
12092
|
+
const multiReturn = method !== "flatMap" ? extractMultiReturnJsxBranches(body, true) : null;
|
|
12093
|
+
if (multiReturn && multiReturn.branches.length > 0) {
|
|
12094
|
+
const loc = getSourceLocation(body, ctx.sourceFile, ctx.filePath);
|
|
12095
|
+
children = [foldMultiReturnBranches(multiReturn, ctx, loc, ctx.getJS)];
|
|
12096
|
+
const pre = multiReturn.preamble ?? [];
|
|
12097
|
+
if (pre.length > 0) {
|
|
12098
|
+
preamble = preambleFromValueStatements(pre, ctx);
|
|
12099
|
+
if (!isClientOnly && !(ctx.analyzer.acceptsCallbackBody?.("map") ?? false)) {
|
|
12100
|
+
ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, loc, {
|
|
12101
|
+
message: "A .map() callback body with a `const`/`let` preamble before its " + "branches cannot be lowered to a template: the loop-local binding " + "cannot be carried into a conditional branch on this backend.",
|
|
12102
|
+
suggestion: {
|
|
12103
|
+
message: "Add /* @client */ to evaluate this expression on the client only"
|
|
12104
|
+
}
|
|
12105
|
+
}));
|
|
12106
|
+
}
|
|
12107
|
+
}
|
|
12108
|
+
}
|
|
12109
|
+
const returnStmt = children.length === 0 ? body.statements.find((s) => ts11.isReturnStatement(s) && s.expression != null) : undefined;
|
|
11809
12110
|
if (returnStmt && returnStmt.expression) {
|
|
11810
12111
|
let returnExpr = returnStmt.expression;
|
|
11811
12112
|
while (ts11.isParenthesizedExpression(returnExpr)) {
|
|
@@ -11817,41 +12118,84 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
|
|
|
11817
12118
|
children = [transformed];
|
|
11818
12119
|
}
|
|
11819
12120
|
}
|
|
11820
|
-
|
|
11821
|
-
const templatePreambleStmts = [];
|
|
11822
|
-
const typedPreambleStmts = [];
|
|
11823
|
-
let hasTypeDiff = false;
|
|
11824
|
-
let hasTemplateDiff = false;
|
|
12121
|
+
let jsxPreambleStmt;
|
|
11825
12122
|
for (const stmt of body.statements) {
|
|
11826
12123
|
if (stmt === returnStmt)
|
|
11827
12124
|
break;
|
|
11828
|
-
|
|
11829
|
-
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
templatePreambleStmts.push(tjs.endsWith(";") ? tjs : tjs + ";");
|
|
11833
|
-
typedPreambleStmts.push(ts12.endsWith(";") ? ts12 : ts12 + ";");
|
|
11834
|
-
if (js !== ts12)
|
|
11835
|
-
hasTypeDiff = true;
|
|
11836
|
-
if (js !== tjs)
|
|
11837
|
-
hasTemplateDiff = true;
|
|
12125
|
+
if (containsJsxInExpression(stmt)) {
|
|
12126
|
+
jsxPreambleStmt = stmt;
|
|
12127
|
+
break;
|
|
12128
|
+
}
|
|
11838
12129
|
}
|
|
11839
|
-
if (
|
|
11840
|
-
|
|
11841
|
-
if (
|
|
11842
|
-
|
|
12130
|
+
if (jsxPreambleStmt) {
|
|
12131
|
+
const jsRuntime = isClientOnly || (ctx.analyzer.acceptsCallbackBody?.("map") ?? false);
|
|
12132
|
+
if (children.length === 0) {
|
|
12133
|
+
ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(jsxPreambleStmt, ctx.sourceFile, ctx.filePath), {
|
|
12134
|
+
message: "A .map() callback that builds JSX in a statement before its " + "`return` must return a single JSX element that embeds the " + "built array — this return shape has no element root to host it.",
|
|
12135
|
+
suggestion: {
|
|
12136
|
+
message: "Wrap the result in one element root, e.g. `return <tr key={item.id}>{out}</tr>`."
|
|
12137
|
+
}
|
|
12138
|
+
}));
|
|
12139
|
+
} else if (!jsRuntime) {
|
|
12140
|
+
ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(jsxPreambleStmt, ctx.sourceFile, ctx.filePath), {
|
|
12141
|
+
message: "A .map() callback that builds JSX in a statement before its " + "`return` (for example pushing elements into an array in a loop) " + "cannot be lowered to a template on this backend.",
|
|
12142
|
+
suggestion: {
|
|
12143
|
+
message: "Add /* @client */ to render this loop on the client only"
|
|
12144
|
+
}
|
|
12145
|
+
}));
|
|
12146
|
+
} else {
|
|
12147
|
+
const collected = buildPreambleSegments(body.statements, returnStmt, ctx);
|
|
12148
|
+
if (collected.refusalNode) {
|
|
12149
|
+
ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(collected.refusalNode, ctx.sourceFile, ctx.filePath), {
|
|
12150
|
+
message: "A JSX element built in a .map() callback preamble cannot carry " + "event handlers, components, nested loops, or reactive expressions, " + "and cannot sit inside a template literal — the verbatim render " + "path builds it once, with no reactive wiring.",
|
|
12151
|
+
suggestion: {
|
|
12152
|
+
message: "Return the element directly (not through a preamble variable), " + "or add /* @client */ to render the loop on the client only."
|
|
12153
|
+
}
|
|
12154
|
+
}));
|
|
12155
|
+
} else {
|
|
12156
|
+
preamble = collected.preamble;
|
|
12157
|
+
}
|
|
12158
|
+
}
|
|
12159
|
+
} else {
|
|
12160
|
+
const valueStmts = [];
|
|
12161
|
+
for (const stmt of body.statements) {
|
|
12162
|
+
if (stmt === returnStmt)
|
|
12163
|
+
break;
|
|
12164
|
+
valueStmts.push(stmt);
|
|
11843
12165
|
}
|
|
11844
|
-
if (
|
|
11845
|
-
|
|
12166
|
+
if (valueStmts.length > 0) {
|
|
12167
|
+
preamble = preambleFromValueStatements(valueStmts, ctx);
|
|
11846
12168
|
}
|
|
11847
12169
|
}
|
|
11848
12170
|
}
|
|
11849
|
-
if (method === "flatMap" && children.length === 0) {
|
|
12171
|
+
if (method === "flatMap" && children.length === 0 && !flatMapProjectionCall(body)) {
|
|
11850
12172
|
flatMapCallback = buildFlatMapCallback(callback, body, ctx);
|
|
11851
12173
|
}
|
|
11852
12174
|
} else {
|
|
11853
12175
|
tryTransformRenderableBody(body);
|
|
11854
12176
|
}
|
|
12177
|
+
if (method === "flatMap" && children.length === 0 && !flatMapCallback) {
|
|
12178
|
+
const projection = flatMapProjectionCall(body);
|
|
12179
|
+
if (projection) {
|
|
12180
|
+
const transformed = transformJsxExpression(projection, ctx, isClientOnly);
|
|
12181
|
+
if (transformed && transformed.type === "loop") {
|
|
12182
|
+
children = [transformed];
|
|
12183
|
+
}
|
|
12184
|
+
}
|
|
12185
|
+
}
|
|
12186
|
+
if (method === "flatMap" && children.length === 0 && !flatMapCallback && !ts11.isBlock(body)) {
|
|
12187
|
+
flatMapCallback = buildFlatMapCallback(callback, body, ctx);
|
|
12188
|
+
}
|
|
12189
|
+
if (flatMapCallback)
|
|
12190
|
+
preamble = undefined;
|
|
12191
|
+
if (flatMapCallback && !isClientOnly && !(ctx.analyzer.acceptsCallbackBody?.("flatMap") ?? false)) {
|
|
12192
|
+
ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(body, ctx.sourceFile, ctx.filePath), {
|
|
12193
|
+
message: "A .flatMap() callback body with statements or a nested projection " + "cannot be lowered to a template on this backend.",
|
|
12194
|
+
suggestion: {
|
|
12195
|
+
message: "Add /* @client */ to render this loop on the client only"
|
|
12196
|
+
}
|
|
12197
|
+
}));
|
|
12198
|
+
}
|
|
11855
12199
|
if (paramBindings) {
|
|
11856
12200
|
for (const b of paramBindings)
|
|
11857
12201
|
ctx.loopParams.delete(b.name);
|
|
@@ -11863,6 +12207,16 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
|
|
|
11863
12207
|
ctx.loopDepth--;
|
|
11864
12208
|
}
|
|
11865
12209
|
if (children.length === 0 && !flatMapCallback) {
|
|
12210
|
+
const cb = node.arguments[0];
|
|
12211
|
+
const cbBody = cb && (ts11.isArrowFunction(cb) || ts11.isFunctionExpression(cb)) ? cb.body : undefined;
|
|
12212
|
+
if (cbBody && containsJsxInExpression(cbBody) && ctx.analyzer.errors.length === diagCountAtEntry) {
|
|
12213
|
+
ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(cbBody, ctx.sourceFile, ctx.filePath), {
|
|
12214
|
+
message: `A .${method}() callback that builds JSX in this shape cannot be ` + "compiled — the JSX would leak verbatim into the client bundle. " + "Recognized bodies: a JSX element/fragment, a ternary or " + "&& / || / ?? expression, an array literal (flatMap), or a block " + "body whose return the compiler can lower.",
|
|
12215
|
+
suggestion: {
|
|
12216
|
+
message: "Restructure the callback to return the JSX element directly " + "(or via a block body with a plain `return`)."
|
|
12217
|
+
}
|
|
12218
|
+
}));
|
|
12219
|
+
}
|
|
11866
12220
|
return null;
|
|
11867
12221
|
}
|
|
11868
12222
|
if (ts11.isArrowFunction(node.arguments[0]) && children.length > 0) {
|
|
@@ -11871,6 +12225,31 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
|
|
|
11871
12225
|
const itemConditional = children.length > 0 ? loopBodyItemConditional(children) : null;
|
|
11872
12226
|
const bodyIsItemConditional = itemConditional !== null;
|
|
11873
12227
|
const key = bodyIsItemConditional ? extractItemConditionalKey(itemConditional) : children.length > 0 ? extractLoopKey(children[0]) : null;
|
|
12228
|
+
const declaredNameSet = preamble && preamble.declaredNames.length > 0 ? new Set(preamble.declaredNames) : undefined;
|
|
12229
|
+
if (key && declaredNameSet) {
|
|
12230
|
+
const keyRefs = extractFreeIdentifiersFromText(key);
|
|
12231
|
+
const usesLocal = [...keyRefs].some((r) => declaredNameSet.has(r));
|
|
12232
|
+
if (usesLocal) {
|
|
12233
|
+
ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(node, ctx.sourceFile, ctx.filePath), {
|
|
12234
|
+
message: "A .map() loop key must be derivable from the loop item — it is " + "evaluated before the callback body runs, so it cannot reference a " + "value computed in the callback preamble.",
|
|
12235
|
+
suggestion: {
|
|
12236
|
+
message: "Derive the key directly from the loop item (e.g. key={item.id})."
|
|
12237
|
+
}
|
|
12238
|
+
}));
|
|
12239
|
+
}
|
|
12240
|
+
}
|
|
12241
|
+
if (preamble && preamble.builderNames.length > 0) {
|
|
12242
|
+
flagArrayChildExpressions(children, new Set(preamble.builderNames));
|
|
12243
|
+
}
|
|
12244
|
+
if (preamble && preamble.builderNames.length > 0 && children.length === 1 && children[0].type === "component") {
|
|
12245
|
+
ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(node, ctx.sourceFile, ctx.filePath), {
|
|
12246
|
+
message: "A .map() callback that builds JSX in its preamble cannot return a " + "component: the built elements would reach the component as raw HTML " + "strings on the client but as JSX elements at SSR (a silent divergence).",
|
|
12247
|
+
suggestion: {
|
|
12248
|
+
message: "Return a plain element root that embeds the array, or move the " + "building logic inside the component."
|
|
12249
|
+
}
|
|
12250
|
+
}));
|
|
12251
|
+
preamble = undefined;
|
|
12252
|
+
}
|
|
11874
12253
|
let childComponent;
|
|
11875
12254
|
if (children.length === 1 && children[0].type === "component") {
|
|
11876
12255
|
const comp = children[0];
|
|
@@ -11890,6 +12269,7 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
|
|
|
11890
12269
|
const hasCalls = exprHasFunctionCalls(arrayExpr);
|
|
11891
12270
|
const isDirectPropArray = method !== "flatMap" && isArrayExprDirectPropRef(arrayExpr, ctx);
|
|
11892
12271
|
const isStaticArray = !isSignalOrMemoArray(array, ctx) && !isDirectPropArray && !hasCalls && !objectIteration;
|
|
12272
|
+
const preambleRegions = preamble && !isStaticArray ? collectPreambleRegions(children, new Set(preamble.declaredNames), ctx) : undefined;
|
|
11893
12273
|
const nestedComponents = collectNestedComponents(children).filter((c) => c.name !== childComponent?.name);
|
|
11894
12274
|
return {
|
|
11895
12275
|
type: "loop",
|
|
@@ -11919,11 +12299,10 @@ function transformMapCall(node, ctx, isClientOnly = false, method = "map") {
|
|
|
11919
12299
|
objectIteration,
|
|
11920
12300
|
depth,
|
|
11921
12301
|
clientOnly: isClientOnly || undefined,
|
|
11922
|
-
|
|
11923
|
-
|
|
12302
|
+
preamble,
|
|
12303
|
+
preambleRegions: preambleRegions && preambleRegions.length > 0 ? preambleRegions : undefined,
|
|
11924
12304
|
paramType,
|
|
11925
12305
|
indexType,
|
|
11926
|
-
typedMapPreamble,
|
|
11927
12306
|
paramBindings,
|
|
11928
12307
|
arrayFreeIdentifiers: extractFreeIdentifiersFromNode(arrayExpr),
|
|
11929
12308
|
flatMapCallback,
|
|
@@ -11959,47 +12338,281 @@ function containsJsx(node) {
|
|
|
11959
12338
|
function buildFlatMapCallback(callback, body, ctx) {
|
|
11960
12339
|
if (!containsJsx(body))
|
|
11961
12340
|
return;
|
|
11962
|
-
const
|
|
11963
|
-
const
|
|
11964
|
-
|
|
11965
|
-
const
|
|
11966
|
-
const bodyText = sourceText.slice(bodyStart, bodyEnd);
|
|
11967
|
-
const jsxNodes = [];
|
|
11968
|
-
function collectJsx(n) {
|
|
12341
|
+
const leafSpans = [];
|
|
12342
|
+
const leafIrs = [];
|
|
12343
|
+
let refusalNode;
|
|
12344
|
+
const collectJsx = (n, underTemplate) => {
|
|
11969
12345
|
if (ts11.isJsxElement(n) || ts11.isJsxSelfClosingElement(n) || ts11.isJsxFragment(n)) {
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
});
|
|
12346
|
+
if (underTemplate)
|
|
12347
|
+
refusalNode ??= n;
|
|
12348
|
+
leafSpans.push({ start: n.getStart(ctx.sourceFile), end: n.getEnd() });
|
|
12349
|
+
const ir = transformNode(n, ctx);
|
|
12350
|
+
leafIrs.push(ir ?? { type: "text", value: "", loc: getSourceLocation(n, ctx.sourceFile, ctx.filePath) });
|
|
11975
12351
|
return;
|
|
11976
12352
|
}
|
|
11977
|
-
n.
|
|
11978
|
-
|
|
11979
|
-
|
|
11980
|
-
|
|
12353
|
+
const inTemplate = underTemplate || ts11.isTemplateExpression(n) || ts11.isTaggedTemplateExpression(n);
|
|
12354
|
+
n.forEachChild((c) => collectJsx(c, inTemplate));
|
|
12355
|
+
};
|
|
12356
|
+
collectJsx(body, false);
|
|
12357
|
+
if (leafSpans.length === 0)
|
|
11981
12358
|
return;
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
11986
|
-
|
|
11987
|
-
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
|
|
11993
|
-
|
|
12359
|
+
if (refusalNode) {
|
|
12360
|
+
ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(refusalNode, ctx.sourceFile, ctx.filePath), {
|
|
12361
|
+
message: "A JSX element inside a template literal in a .flatMap() callback " + "body cannot be compiled.",
|
|
12362
|
+
suggestion: { message: "Build the element outside the template literal." }
|
|
12363
|
+
}));
|
|
12364
|
+
return;
|
|
12365
|
+
}
|
|
12366
|
+
for (const leafIr of leafIrs) {
|
|
12367
|
+
if (leafIr.type !== "element") {
|
|
12368
|
+
const loc = "loc" in leafIr && leafIr.loc ? leafIr.loc : getSourceLocation(body, ctx.sourceFile, ctx.filePath);
|
|
12369
|
+
ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, loc, {
|
|
12370
|
+
message: "A JSX leaf produced by a .flatMap() callback must be a single " + "element — a fragment or non-element root cannot ride the keyed " + "descriptor path (each leaf hydrates and patches as one element).",
|
|
12371
|
+
suggestion: {
|
|
12372
|
+
message: "Wrap the leaf content in a single keyed element."
|
|
12373
|
+
}
|
|
12374
|
+
}));
|
|
12375
|
+
return;
|
|
12376
|
+
}
|
|
12377
|
+
if (flatMapLeafNeedsWiring(leafIr)) {
|
|
12378
|
+
const loc = "loc" in leafIr && leafIr.loc ? leafIr.loc : getSourceLocation(body, ctx.sourceFile, ctx.filePath);
|
|
12379
|
+
ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, loc, {
|
|
12380
|
+
message: "A JSX element produced by a .flatMap() callback cannot carry " + "event handlers, components, nested loops, or spreads — the " + "leaf renders as a keyed HTML string with no per-element wiring.",
|
|
12381
|
+
suggestion: {
|
|
12382
|
+
message: "Restructure so the interactive element lives in a .map() body — " + "the descriptor path has no per-element wiring on any backend, " + "so /* @client */ does not lift this."
|
|
12383
|
+
}
|
|
12384
|
+
}));
|
|
12385
|
+
return;
|
|
12386
|
+
}
|
|
11994
12387
|
}
|
|
11995
|
-
|
|
12388
|
+
const pieces = reconstructAsSegments(body, ctx.sourceFile, ctx.analyzer.typeExcludeRanges, leafSpans);
|
|
12389
|
+
const segments = pieces.map((piece) => {
|
|
12390
|
+
if ("marker" in piece)
|
|
12391
|
+
return { kind: "jsx", ir: leafIrs[piece.marker] };
|
|
12392
|
+
const tpl = rewriteBarePropRefs2(piece.js, body, ctx);
|
|
12393
|
+
return tpl !== undefined && tpl !== piece.js ? { kind: "js", text: piece.js, templateText: tpl } : { kind: "js", text: piece.js };
|
|
12394
|
+
});
|
|
11996
12395
|
const paramsText = callback.parameters.map((p) => p.getText(ctx.sourceFile)).join(", ");
|
|
11997
12396
|
return {
|
|
11998
12397
|
params: `(${paramsText})`,
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12398
|
+
segments,
|
|
12399
|
+
rawBody: tsxSourceText(body.getText(ctx.sourceFile))
|
|
12400
|
+
};
|
|
12401
|
+
}
|
|
12402
|
+
function flatMapLeafNeedsWiring(ir) {
|
|
12403
|
+
switch (ir.type) {
|
|
12404
|
+
case "component":
|
|
12405
|
+
case "loop":
|
|
12406
|
+
return true;
|
|
12407
|
+
case "element":
|
|
12408
|
+
if (ir.events.length > 0)
|
|
12409
|
+
return true;
|
|
12410
|
+
if (ir.attrs.some((a) => a.name.startsWith("...")))
|
|
12411
|
+
return true;
|
|
12412
|
+
return ir.children.some(flatMapLeafNeedsWiring);
|
|
12413
|
+
case "conditional":
|
|
12414
|
+
return flatMapLeafNeedsWiring(ir.whenTrue) || (ir.whenFalse ? flatMapLeafNeedsWiring(ir.whenFalse) : false);
|
|
12415
|
+
case "fragment":
|
|
12416
|
+
return ir.children.some(flatMapLeafNeedsWiring);
|
|
12417
|
+
default:
|
|
12418
|
+
return false;
|
|
12419
|
+
}
|
|
12420
|
+
}
|
|
12421
|
+
function preambleFragmentNeedsWiring(ir) {
|
|
12422
|
+
switch (ir.type) {
|
|
12423
|
+
case "component":
|
|
12424
|
+
case "loop":
|
|
12425
|
+
return true;
|
|
12426
|
+
case "element":
|
|
12427
|
+
if (ir.events.length > 0)
|
|
12428
|
+
return true;
|
|
12429
|
+
if (ir.attrs.some((a) => a.name.startsWith("...")))
|
|
12430
|
+
return true;
|
|
12431
|
+
return ir.children.some(preambleFragmentNeedsWiring);
|
|
12432
|
+
case "expression":
|
|
12433
|
+
return ir.reactive === true;
|
|
12434
|
+
case "conditional":
|
|
12435
|
+
return preambleFragmentNeedsWiring(ir.whenTrue) || (ir.whenFalse ? preambleFragmentNeedsWiring(ir.whenFalse) : false);
|
|
12436
|
+
case "fragment":
|
|
12437
|
+
return ir.children.some(preambleFragmentNeedsWiring);
|
|
12438
|
+
default:
|
|
12439
|
+
return false;
|
|
12440
|
+
}
|
|
12441
|
+
}
|
|
12442
|
+
function flagArrayChildExpressions(nodes, declared) {
|
|
12443
|
+
for (const node of nodes) {
|
|
12444
|
+
switch (node.type) {
|
|
12445
|
+
case "expression": {
|
|
12446
|
+
const name = node.expr.trim();
|
|
12447
|
+
const refs = extractFreeIdentifiersFromText(node.expr);
|
|
12448
|
+
if (refs.size === 1 && refs.has(name) && declared.has(name)) {
|
|
12449
|
+
node.joinArrayChild = true;
|
|
12450
|
+
}
|
|
12451
|
+
break;
|
|
12452
|
+
}
|
|
12453
|
+
case "element":
|
|
12454
|
+
case "fragment":
|
|
12455
|
+
flagArrayChildExpressions(node.children, declared);
|
|
12456
|
+
break;
|
|
12457
|
+
case "conditional":
|
|
12458
|
+
flagArrayChildExpressions([node.whenTrue, ...node.whenFalse ? [node.whenFalse] : []], declared);
|
|
12459
|
+
break;
|
|
12460
|
+
}
|
|
12461
|
+
}
|
|
12462
|
+
}
|
|
12463
|
+
function collectPreambleRegions(nodes, declared, ctx) {
|
|
12464
|
+
const regions = [];
|
|
12465
|
+
const visit2 = (list) => {
|
|
12466
|
+
for (const node of list) {
|
|
12467
|
+
switch (node.type) {
|
|
12468
|
+
case "expression": {
|
|
12469
|
+
const refs = extractFreeIdentifiersFromText(node.expr);
|
|
12470
|
+
const usesPreambleLocal = [...refs].some((r) => declared.has(r));
|
|
12471
|
+
if (usesPreambleLocal) {
|
|
12472
|
+
if (!node.slotId)
|
|
12473
|
+
node.slotId = generateSlotId(ctx);
|
|
12474
|
+
node.preambleRegion = true;
|
|
12475
|
+
node.reactive = true;
|
|
12476
|
+
regions.push({
|
|
12477
|
+
slotId: node.slotId,
|
|
12478
|
+
expr: node.expr,
|
|
12479
|
+
joinArrayChild: node.joinArrayChild || undefined
|
|
12480
|
+
});
|
|
12481
|
+
}
|
|
12482
|
+
break;
|
|
12483
|
+
}
|
|
12484
|
+
case "element":
|
|
12485
|
+
case "fragment":
|
|
12486
|
+
visit2(node.children);
|
|
12487
|
+
break;
|
|
12488
|
+
case "conditional":
|
|
12489
|
+
visit2([node.whenTrue, ...node.whenFalse ? [node.whenFalse] : []]);
|
|
12490
|
+
break;
|
|
12491
|
+
}
|
|
12492
|
+
}
|
|
12493
|
+
};
|
|
12494
|
+
visit2(nodes);
|
|
12495
|
+
return regions;
|
|
12496
|
+
}
|
|
12497
|
+
function collectBindingNames(name, out) {
|
|
12498
|
+
if (ts11.isIdentifier(name)) {
|
|
12499
|
+
out.add(name.text);
|
|
12500
|
+
return;
|
|
12501
|
+
}
|
|
12502
|
+
for (const el of name.elements) {
|
|
12503
|
+
if (ts11.isBindingElement(el))
|
|
12504
|
+
collectBindingNames(el.name, out);
|
|
12505
|
+
}
|
|
12506
|
+
}
|
|
12507
|
+
function collectPreambleDeclaredNames(stmt, out) {
|
|
12508
|
+
if (ts11.isVariableStatement(stmt)) {
|
|
12509
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
12510
|
+
collectBindingNames(decl.name, out);
|
|
12511
|
+
}
|
|
12512
|
+
} else if (ts11.isFunctionDeclaration(stmt) && stmt.name) {
|
|
12513
|
+
out.add(stmt.name.text);
|
|
12514
|
+
}
|
|
12515
|
+
}
|
|
12516
|
+
function preambleFromValueStatements(statements, ctx) {
|
|
12517
|
+
const segments = [];
|
|
12518
|
+
const typedParts = [];
|
|
12519
|
+
const declared = new Set;
|
|
12520
|
+
for (const stmt of statements) {
|
|
12521
|
+
collectPreambleDeclaredNames(stmt, declared);
|
|
12522
|
+
const js0 = ctx.getJS(stmt);
|
|
12523
|
+
const tjs0 = ctx.getTemplateJS(stmt);
|
|
12524
|
+
const raw0 = stmt.getText(ctx.sourceFile);
|
|
12525
|
+
const js = (js0.endsWith(";") ? js0 : js0 + ";") + " ";
|
|
12526
|
+
const tjs = (tjs0.endsWith(";") ? tjs0 : tjs0 + ";") + " ";
|
|
12527
|
+
typedParts.push(raw0.endsWith(";") ? raw0 : raw0 + ";");
|
|
12528
|
+
segments.push(tjs !== js ? { kind: "js", text: js, templateText: tjs } : { kind: "js", text: js });
|
|
12529
|
+
}
|
|
12530
|
+
return {
|
|
12531
|
+
segments: trimPreambleSegments(segments),
|
|
12532
|
+
ssrText: tsxSourceText(typedParts.join(" ")),
|
|
12533
|
+
declaredNames: [...declared],
|
|
12534
|
+
builderNames: []
|
|
12535
|
+
};
|
|
12536
|
+
}
|
|
12537
|
+
function trimPreambleSegments(segments) {
|
|
12538
|
+
const last = segments[segments.length - 1];
|
|
12539
|
+
if (last?.kind === "js") {
|
|
12540
|
+
const text = last.text.trimEnd();
|
|
12541
|
+
const templateText = last.templateText?.trimEnd();
|
|
12542
|
+
segments[segments.length - 1] = templateText !== undefined ? { kind: "js", text, templateText } : { kind: "js", text };
|
|
12543
|
+
}
|
|
12544
|
+
return segments;
|
|
12545
|
+
}
|
|
12546
|
+
function buildPreambleSegments(statements, returnStmt, ctx) {
|
|
12547
|
+
const segments = [];
|
|
12548
|
+
const typedParts = [];
|
|
12549
|
+
const declared = new Set;
|
|
12550
|
+
const builders = new Set;
|
|
12551
|
+
let refusalNode;
|
|
12552
|
+
const recordBuilderTarget = (leaf, stmt) => {
|
|
12553
|
+
for (let n = leaf.parent;n && n !== stmt.parent; n = n.parent) {
|
|
12554
|
+
if (ts11.isCallExpression(n) && ts11.isPropertyAccessExpression(n.expression) && (n.expression.name.text === "push" || n.expression.name.text === "unshift") && ts11.isIdentifier(n.expression.expression)) {
|
|
12555
|
+
builders.add(n.expression.expression.text);
|
|
12556
|
+
return;
|
|
12557
|
+
}
|
|
12558
|
+
if (ts11.isVariableDeclaration(n) && ts11.isIdentifier(n.name)) {
|
|
12559
|
+
builders.add(n.name.text);
|
|
12560
|
+
return;
|
|
12561
|
+
}
|
|
12562
|
+
}
|
|
12563
|
+
};
|
|
12564
|
+
for (const stmt of statements) {
|
|
12565
|
+
if (stmt === returnStmt)
|
|
12566
|
+
break;
|
|
12567
|
+
collectPreambleDeclaredNames(stmt, declared);
|
|
12568
|
+
const leafSpans = [];
|
|
12569
|
+
const leafIrs = [];
|
|
12570
|
+
const collect = (n, underTemplate) => {
|
|
12571
|
+
if (ts11.isJsxElement(n) || ts11.isJsxSelfClosingElement(n) || ts11.isJsxFragment(n)) {
|
|
12572
|
+
if (underTemplate)
|
|
12573
|
+
refusalNode ??= n;
|
|
12574
|
+
recordBuilderTarget(n, stmt);
|
|
12575
|
+
leafSpans.push({ start: n.getStart(ctx.sourceFile), end: n.getEnd() });
|
|
12576
|
+
const ir = transformNode(n, ctx);
|
|
12577
|
+
if (ir && preambleFragmentNeedsWiring(ir))
|
|
12578
|
+
refusalNode ??= n;
|
|
12579
|
+
leafIrs.push(ir ?? { type: "text", value: "", loc: getSourceLocation(n, ctx.sourceFile, ctx.filePath) });
|
|
12580
|
+
return;
|
|
12581
|
+
}
|
|
12582
|
+
const inTemplate = underTemplate || ts11.isTemplateExpression(n) || ts11.isTaggedTemplateExpression(n);
|
|
12583
|
+
n.forEachChild((c) => collect(c, inTemplate));
|
|
12584
|
+
};
|
|
12585
|
+
collect(stmt, false);
|
|
12586
|
+
const raw0 = stmt.getText(ctx.sourceFile);
|
|
12587
|
+
typedParts.push(raw0.endsWith(";") ? raw0 : raw0 + ";");
|
|
12588
|
+
if (leafSpans.length === 0) {
|
|
12589
|
+
const js0 = ctx.getJS(stmt);
|
|
12590
|
+
const tjs0 = ctx.getTemplateJS(stmt);
|
|
12591
|
+
const js = (js0.endsWith(";") ? js0 : js0 + ";") + " ";
|
|
12592
|
+
const tjs = (tjs0.endsWith(";") ? tjs0 : tjs0 + ";") + " ";
|
|
12593
|
+
segments.push(tjs !== js ? { kind: "js", text: js, templateText: tjs } : { kind: "js", text: js });
|
|
12594
|
+
continue;
|
|
12595
|
+
}
|
|
12596
|
+
const pieces = reconstructAsSegments(stmt, ctx.sourceFile, ctx.analyzer.typeExcludeRanges, leafSpans);
|
|
12597
|
+
for (const piece of pieces) {
|
|
12598
|
+
if ("marker" in piece) {
|
|
12599
|
+
segments.push({ kind: "jsx", ir: leafIrs[piece.marker] });
|
|
12600
|
+
} else {
|
|
12601
|
+
const tpl = rewriteBarePropRefs2(piece.js, stmt, ctx);
|
|
12602
|
+
segments.push(tpl !== undefined && tpl !== piece.js ? { kind: "js", text: piece.js, templateText: tpl } : { kind: "js", text: piece.js });
|
|
12603
|
+
}
|
|
12604
|
+
}
|
|
12605
|
+
const sep2 = raw0.endsWith(";") ? " " : "; ";
|
|
12606
|
+
segments.push({ kind: "js", text: sep2 });
|
|
12607
|
+
}
|
|
12608
|
+
return {
|
|
12609
|
+
preamble: {
|
|
12610
|
+
segments: trimPreambleSegments(segments),
|
|
12611
|
+
ssrText: tsxSourceText(typedParts.join(" ")),
|
|
12612
|
+
declaredNames: [...declared],
|
|
12613
|
+
builderNames: [...builders]
|
|
12614
|
+
},
|
|
12615
|
+
refusalNode
|
|
12003
12616
|
};
|
|
12004
12617
|
}
|
|
12005
12618
|
function collectNestedComponents(nodes) {
|
|
@@ -13258,6 +13871,8 @@ function collectLoopChildReactiveTexts(node, ctx, loopParam, loopParamBindings,
|
|
|
13258
13871
|
expression: ({ node: n, scope: insideConditional }) => {
|
|
13259
13872
|
if (!n.slotId)
|
|
13260
13873
|
return;
|
|
13874
|
+
if (n.preambleRegion)
|
|
13875
|
+
return;
|
|
13261
13876
|
const originFreeIds = freeIdsFromRefs(n.origin?.freeRefs);
|
|
13262
13877
|
const expanded = expandConstantForReactivity(n.expr, ctx, originFreeIds);
|
|
13263
13878
|
const reactive = classifyReactivity(expanded.expr, ctx, loopParam, loopParamBindings, expanded.freeIds).kind !== "none" || decideWrapFromAstFlags(n).wrap;
|
|
@@ -13642,7 +14257,7 @@ function collectInnerLoops(nodes, siblingOffsets, outerLoopParam, ctx, options)
|
|
|
13642
14257
|
objectIteration: n.objectIteration,
|
|
13643
14258
|
containerSlotId: scope.parentSlotId,
|
|
13644
14259
|
template,
|
|
13645
|
-
|
|
14260
|
+
preamble: n.preamble,
|
|
13646
14261
|
refsOuterParam: refsOuter,
|
|
13647
14262
|
childComponents,
|
|
13648
14263
|
insideConditional: !flat && scope.insideCond ? true : undefined,
|
|
@@ -13813,10 +14428,13 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
|
|
|
13813
14428
|
loop: ({ node: l, scope: inCond }) => {
|
|
13814
14429
|
if (!l.slotId || inCond)
|
|
13815
14430
|
return;
|
|
14431
|
+
const projectionInner = l.method === "flatMap" && l.children.length === 1 && l.children[0].type === "loop" ? l.children[0] : undefined;
|
|
13816
14432
|
const childHandlers = [];
|
|
13817
|
-
const bindings = collectLoopChildBindings(l.children, ctx, siblingOffsets, l.param, l.paramBindings);
|
|
13818
|
-
|
|
13819
|
-
|
|
14433
|
+
const bindings = projectionInner ? emptyLoopChildBindings() : collectLoopChildBindings(l.children, ctx, siblingOffsets, l.param, l.paramBindings);
|
|
14434
|
+
if (!projectionInner) {
|
|
14435
|
+
for (const child of l.children) {
|
|
14436
|
+
childHandlers.push(...collectEventHandlersFromIR(child));
|
|
14437
|
+
}
|
|
13820
14438
|
}
|
|
13821
14439
|
if (l.childComponent) {
|
|
13822
14440
|
for (const prop of l.childComponent.props) {
|
|
@@ -13827,7 +14445,7 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
|
|
|
13827
14445
|
}
|
|
13828
14446
|
}
|
|
13829
14447
|
}
|
|
13830
|
-
const { useElementReconciliation, innerLoops } = decideLoopRendering(l, siblingOffsets, ctx);
|
|
14448
|
+
const { useElementReconciliation, innerLoops } = projectionInner ? { useElementReconciliation: false, innerLoops: undefined } : decideLoopRendering(l, siblingOffsets, ctx);
|
|
13831
14449
|
let template = "";
|
|
13832
14450
|
let staticItemTemplate;
|
|
13833
14451
|
let skeletonTemplate;
|
|
@@ -13837,7 +14455,7 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
|
|
|
13837
14455
|
if (l.isStaticArray && l.children[0]) {
|
|
13838
14456
|
staticItemTemplate = irToHtmlTemplate(l.children[0], buildRestSpreadNames(ctx), 0, undefined, undefined, true);
|
|
13839
14457
|
}
|
|
13840
|
-
} else if (l.children[0]) {
|
|
14458
|
+
} else if (l.children[0] && !projectionInner) {
|
|
13841
14459
|
const loopParamSpec = [{ param: l.param, bindings: l.paramBindings }];
|
|
13842
14460
|
template = useElementReconciliation ? irToPlaceholderTemplate(l.children[0], buildRestSpreadNames(ctx), 0, loopParamSpec) : irToHtmlTemplate(l.children[0], buildRestSpreadNames(ctx), 0, loopParamSpec);
|
|
13843
14461
|
if (l.isStaticArray) {
|
|
@@ -13889,7 +14507,17 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
|
|
|
13889
14507
|
raw: l.sortComparator.raw
|
|
13890
14508
|
} : undefined,
|
|
13891
14509
|
chainOrder: l.chainOrder,
|
|
13892
|
-
|
|
14510
|
+
preamble: l.preamble,
|
|
14511
|
+
preambleRegions: l.preambleRegions,
|
|
14512
|
+
flatMapClient: projectionInner ? {
|
|
14513
|
+
params: l.index ? `(${l.param}, ${l.index})` : `(${l.param})`,
|
|
14514
|
+
body: renderFlatMapProjectionClientBody(projectionInner, buildRestSpreadNames(ctx)),
|
|
14515
|
+
keyed: projectionInner.key !== null
|
|
14516
|
+
} : l.flatMapCallback ? {
|
|
14517
|
+
params: l.flatMapCallback.params,
|
|
14518
|
+
body: renderFlatMapClientBody(l.flatMapCallback, buildRestSpreadNames(ctx)),
|
|
14519
|
+
keyed: flatMapCallbackHasKeyedLeaf(l.flatMapCallback)
|
|
14520
|
+
} : undefined
|
|
13893
14521
|
});
|
|
13894
14522
|
},
|
|
13895
14523
|
component: ({ node: c, descend, descendJsxChildren }) => {
|
|
@@ -14046,15 +14674,18 @@ function collectBranchLoops(node, ctx, siblingOffsets) {
|
|
|
14046
14674
|
const containerSlot = parentSlotId ?? n.slotId;
|
|
14047
14675
|
if (!containerSlot)
|
|
14048
14676
|
return;
|
|
14049
|
-
const
|
|
14677
|
+
const projectionInner = n.method === "flatMap" && n.children.length === 1 && n.children[0].type === "loop" ? n.children[0] : undefined;
|
|
14678
|
+
const { useElementReconciliation, innerLoops: innerLoopsCollected } = projectionInner ? { useElementReconciliation: false, innerLoops: undefined } : decideLoopRendering(n, siblingOffsets, undefined);
|
|
14050
14679
|
let childTemplate;
|
|
14051
14680
|
const branchLoopParamSpec = [{ param: n.param, bindings: n.paramBindings }];
|
|
14052
|
-
if (
|
|
14681
|
+
if (projectionInner) {
|
|
14682
|
+
childTemplate = "";
|
|
14683
|
+
} else if (useElementReconciliation && n.children[0]) {
|
|
14053
14684
|
childTemplate = irToPlaceholderTemplate(n.children[0], restNames, 0, branchLoopParamSpec);
|
|
14054
14685
|
} else {
|
|
14055
14686
|
childTemplate = n.children.map((c) => irToHtmlTemplate(c, undefined, 0, branchLoopParamSpec)).join("");
|
|
14056
14687
|
}
|
|
14057
|
-
const branchBindings = ctx ? collectLoopChildBindings(n.children, ctx, siblingOffsets, n.param, n.paramBindings) : emptyLoopChildBindings();
|
|
14688
|
+
const branchBindings = ctx && !projectionInner ? collectLoopChildBindings(n.children, ctx, siblingOffsets, n.param, n.paramBindings) : emptyLoopChildBindings();
|
|
14058
14689
|
loops.push({
|
|
14059
14690
|
kind: "branch",
|
|
14060
14691
|
array: n.array,
|
|
@@ -14070,7 +14701,8 @@ function collectBranchLoops(node, ctx, siblingOffsets) {
|
|
|
14070
14701
|
objectIteration: n.objectIteration,
|
|
14071
14702
|
template: childTemplate,
|
|
14072
14703
|
containerSlotId: containerSlot,
|
|
14073
|
-
|
|
14704
|
+
preamble: n.preamble,
|
|
14705
|
+
preambleRegions: n.preambleRegions,
|
|
14074
14706
|
nestedComponents: useElementReconciliation ? n.nestedComponents : undefined,
|
|
14075
14707
|
bindings: branchBindings,
|
|
14076
14708
|
innerLoops: useElementReconciliation ? innerLoopsCollected : undefined,
|
|
@@ -14084,7 +14716,16 @@ function collectBranchLoops(node, ctx, siblingOffsets) {
|
|
|
14084
14716
|
paramB: n.sortComparator.paramB,
|
|
14085
14717
|
raw: n.sortComparator.raw
|
|
14086
14718
|
} : undefined,
|
|
14087
|
-
chainOrder: n.chainOrder
|
|
14719
|
+
chainOrder: n.chainOrder,
|
|
14720
|
+
flatMapClient: projectionInner ? {
|
|
14721
|
+
params: n.index ? `(${n.param}, ${n.index})` : `(${n.param})`,
|
|
14722
|
+
body: renderFlatMapProjectionClientBody(projectionInner, restNames),
|
|
14723
|
+
keyed: projectionInner.key !== null
|
|
14724
|
+
} : n.flatMapCallback ? {
|
|
14725
|
+
params: n.flatMapCallback.params,
|
|
14726
|
+
body: renderFlatMapClientBody(n.flatMapCallback, restNames),
|
|
14727
|
+
keyed: flatMapCallbackHasKeyedLeaf(n.flatMapCallback)
|
|
14728
|
+
} : undefined
|
|
14088
14729
|
});
|
|
14089
14730
|
}
|
|
14090
14731
|
});
|
|
@@ -14346,8 +14987,8 @@ function buildReferencesGraph(ctx, irRoot) {
|
|
|
14346
14987
|
addExprEdges(ROOT_SOURCE, elem.filterPredicate.raw, "template-closure");
|
|
14347
14988
|
if (elem.sortComparator)
|
|
14348
14989
|
addExprEdges(ROOT_SOURCE, elem.sortComparator.raw, "template-closure");
|
|
14349
|
-
if (elem.
|
|
14350
|
-
addExprEdges(ROOT_SOURCE, elem.
|
|
14990
|
+
if (elem.preamble)
|
|
14991
|
+
addExprEdges(ROOT_SOURCE, preambleAnalysisText(elem.preamble), "template-closure");
|
|
14351
14992
|
for (const attr of elem.bindings.reactiveAttrs) {
|
|
14352
14993
|
addExprEdges(ROOT_SOURCE, attr.expression, "template-closure");
|
|
14353
14994
|
}
|
|
@@ -14457,8 +15098,15 @@ function buildReferencesGraph(ctx, irRoot) {
|
|
|
14457
15098
|
addExprEdges(ROOT_SOURCE, l.filterPredicate.raw, "template-closure");
|
|
14458
15099
|
if (l.sortComparator)
|
|
14459
15100
|
addExprEdges(ROOT_SOURCE, l.sortComparator.raw, "template-closure");
|
|
14460
|
-
if (l.
|
|
14461
|
-
addExprEdges(ROOT_SOURCE, l.
|
|
15101
|
+
if (l.preamble)
|
|
15102
|
+
addExprEdges(ROOT_SOURCE, preambleAnalysisText(l.preamble), "template-closure");
|
|
15103
|
+
if (l.flatMapCallback) {
|
|
15104
|
+
addExprEdges(ROOT_SOURCE, preambleAnalysisText(l.flatMapCallback), "template-closure");
|
|
15105
|
+
for (const seg of l.flatMapCallback.segments) {
|
|
15106
|
+
if (seg.kind === "jsx")
|
|
15107
|
+
walkIR(seg.ir, null, visitor);
|
|
15108
|
+
}
|
|
15109
|
+
}
|
|
14462
15110
|
descend();
|
|
14463
15111
|
if (l.childComponent)
|
|
14464
15112
|
walkChildComponent(l.childComponent);
|
|
@@ -14636,6 +15284,8 @@ var RUNTIME_IMPORT_CANDIDATES = [
|
|
|
14636
15284
|
"getLoopNodes",
|
|
14637
15285
|
"mapArray",
|
|
14638
15286
|
"mapArrayAnchored",
|
|
15287
|
+
"patchLeaf",
|
|
15288
|
+
"patchSlotRange",
|
|
14639
15289
|
"createDisposableEffect",
|
|
14640
15290
|
"createComponent",
|
|
14641
15291
|
"renderChild",
|
|
@@ -16567,7 +17217,7 @@ function nestedLoopReferencesIndex(inner, comps, events) {
|
|
|
16567
17217
|
if (exprRefs(a.expression, a.freeIdentifiers))
|
|
16568
17218
|
return true;
|
|
16569
17219
|
}
|
|
16570
|
-
if (inner.
|
|
17220
|
+
if (inner.preamble && extractFreeIdentifiersFromStatementText(preambleAnalysisText(inner.preamble)).has(index))
|
|
16571
17221
|
return true;
|
|
16572
17222
|
if (inner.template && extractFreeIdentifiersFromTemplateText(inner.template).has(index))
|
|
16573
17223
|
return true;
|
|
@@ -16638,6 +17288,15 @@ function destructureLoopParam(param, paramBindings) {
|
|
|
16638
17288
|
}
|
|
16639
17289
|
return { head: param, unwrap: "" };
|
|
16640
17290
|
}
|
|
17291
|
+
function buildPreambleRegionPlans(regions, loopParam, loopParamBindings) {
|
|
17292
|
+
if (!regions || regions.length === 0)
|
|
17293
|
+
return [];
|
|
17294
|
+
return regions.map((r) => {
|
|
17295
|
+
const wrapped = wrapLoopParamAsAccessor(r.expr, loopParam, loopParamBindings);
|
|
17296
|
+
const valueExpr = r.joinArrayChild ? `Array.isArray(${wrapped}) ? ${wrapped}.join('') : (${wrapped} ?? '')` : `escapeText(${wrapped})`;
|
|
17297
|
+
return { slotId: r.slotId, valueExpr };
|
|
17298
|
+
});
|
|
17299
|
+
}
|
|
16641
17300
|
function buildComponentPropsExpr2(comp, loopParam, loopParamBindings) {
|
|
16642
17301
|
const wrap = loopParam ? (expr) => wrapLoopParamAsAccessor(expr, loopParam, loopParamBindings) : (expr) => expr;
|
|
16643
17302
|
const entries = comp.props.map((p) => {
|
|
@@ -16722,6 +17381,11 @@ function emitComponentAndEventSetup(ls, indent, elVar, comps, events, loopParam,
|
|
|
16722
17381
|
}
|
|
16723
17382
|
|
|
16724
17383
|
// src/ir-to-client-js/plan/build-static-array-child-init.ts
|
|
17384
|
+
function staticPreludeStatements(preamble) {
|
|
17385
|
+
return preamble ? [renderPreamble(preamble, {
|
|
17386
|
+
renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, undefined, undefined, true)
|
|
17387
|
+
})] : [];
|
|
17388
|
+
}
|
|
16725
17389
|
function buildStaticArrayChildInitsPlan(ctx) {
|
|
16726
17390
|
const plans = [];
|
|
16727
17391
|
for (const elem of ctx.loopElements) {
|
|
@@ -16759,7 +17423,7 @@ function buildSingleCompPlan(elem, childComponent) {
|
|
|
16759
17423
|
arrayExpr: elem.array,
|
|
16760
17424
|
param: elem.param,
|
|
16761
17425
|
indexParam: elem.index || "__idx",
|
|
16762
|
-
outerPreludeStatements: elem.
|
|
17426
|
+
outerPreludeStatements: staticPreludeStatements(elem.preamble),
|
|
16763
17427
|
propsExpr: buildStaticPropsExpr(props)
|
|
16764
17428
|
};
|
|
16765
17429
|
}
|
|
@@ -16774,7 +17438,7 @@ function buildOuterNestedPlan(elem, comp) {
|
|
|
16774
17438
|
param: elem.param,
|
|
16775
17439
|
indexParam,
|
|
16776
17440
|
offsetExpr: buildLoopChildIndexExpr(indexParam, elem.offset),
|
|
16777
|
-
outerPreludeStatements: elem.
|
|
17441
|
+
outerPreludeStatements: staticPreludeStatements(elem.preamble),
|
|
16778
17442
|
propsExpr: buildStaticPropsExpr(comp.props)
|
|
16779
17443
|
};
|
|
16780
17444
|
}
|
|
@@ -16793,13 +17457,13 @@ function buildInnerLoopNestedPlan(elem, innerLoop, innerComps) {
|
|
|
16793
17457
|
outerParam: elem.param,
|
|
16794
17458
|
outerIndexParam,
|
|
16795
17459
|
outerOffsetExpr: buildLoopChildIndexExpr(outerIndexParam, elem.offset),
|
|
16796
|
-
outerPreludeStatements: elem.
|
|
17460
|
+
outerPreludeStatements: staticPreludeStatements(elem.preamble),
|
|
16797
17461
|
innerContainerSlotId: innerLoop.containerSlotId ?? null,
|
|
16798
17462
|
innerArrayExpr: innerLoop.array,
|
|
16799
17463
|
innerParam: innerLoop.param,
|
|
16800
17464
|
innerIndexParam,
|
|
16801
17465
|
innerOffsetExpr: buildLoopChildIndexExpr(innerIndexParam, innerLoop.offset),
|
|
16802
|
-
innerPreludeStatements: innerLoop.
|
|
17466
|
+
innerPreludeStatements: staticPreludeStatements(innerLoop.preamble),
|
|
16803
17467
|
depth: innerLoop.depth,
|
|
16804
17468
|
comps
|
|
16805
17469
|
};
|
|
@@ -16816,11 +17480,11 @@ function buildComponentRootedInnerLoopPlan(elem, innerLoop, innerComps) {
|
|
|
16816
17480
|
outerArrayExpr: elem.array,
|
|
16817
17481
|
outerParam: elem.param,
|
|
16818
17482
|
outerIndexParam: elem.index,
|
|
16819
|
-
outerPreludeStatements: elem.
|
|
17483
|
+
outerPreludeStatements: staticPreludeStatements(elem.preamble),
|
|
16820
17484
|
innerArrayExpr: innerLoop.array,
|
|
16821
17485
|
innerParam: innerLoop.param,
|
|
16822
17486
|
innerIndexParam: innerLoop.index,
|
|
16823
|
-
innerPreludeStatements: innerLoop.
|
|
17487
|
+
innerPreludeStatements: staticPreludeStatements(innerLoop.preamble),
|
|
16824
17488
|
depth: innerLoop.depth,
|
|
16825
17489
|
comps
|
|
16826
17490
|
};
|
|
@@ -17389,7 +18053,7 @@ function buildInnerLoopsPlan(args) {
|
|
|
17389
18053
|
const containerExpr = inner.containerSlotId ? `qsa(${parentElVar}, '[bf="${inner.containerSlotId}"]')` : parentElVar;
|
|
17390
18054
|
const refsParent = !!outerLoopParam && (inner.arrayFreeIdentifiers?.has(outerLoopParam) ?? false);
|
|
17391
18055
|
const useReactive = refsParent && !!inner.template;
|
|
17392
|
-
const emit = useReactive ? buildReactiveEmit(inner, level, wrapOuter, uidSuffix) : buildStaticEmit(inner, level, uidSuffix);
|
|
18056
|
+
const emit = useReactive ? buildReactiveEmit(inner, level, wrapOuter, uidSuffix, outerLoopParam, outerLoopParamBindings) : buildStaticEmit(inner, level, uidSuffix);
|
|
17393
18057
|
const arrayExpr = useReactive ? wrapOuter(inner.array) : inner.array;
|
|
17394
18058
|
const childLevelsPlan = childLevels.length > 0 ? buildInnerLoopsPlan({
|
|
17395
18059
|
levels: childLevels,
|
|
@@ -17415,7 +18079,7 @@ function buildInnerLoopsPlan(args) {
|
|
|
17415
18079
|
}
|
|
17416
18080
|
return plan;
|
|
17417
18081
|
}
|
|
17418
|
-
function buildReactiveEmit(inner, level, wrapOuter, uidSuffix) {
|
|
18082
|
+
function buildReactiveEmit(inner, level, wrapOuter, uidSuffix, outerLoopParam, outerLoopParamBindings) {
|
|
17419
18083
|
const wrapInner = (expr) => wrapLoopParamAsAccessor(expr, inner.param, inner.paramBindings);
|
|
17420
18084
|
const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(inner.param, inner.paramBindings);
|
|
17421
18085
|
const wrappedKey = inner.key ? wrapLoopParamAsAccessor(inner.key, inner.param, inner.paramBindings) : null;
|
|
@@ -17467,8 +18131,16 @@ function buildReactiveEmit(inner, level, wrapOuter, uidSuffix) {
|
|
|
17467
18131
|
preludeStatements.push(indexAlias);
|
|
17468
18132
|
if (paramUnwrap)
|
|
17469
18133
|
preludeStatements.push(paramUnwrap);
|
|
17470
|
-
if (inner.
|
|
17471
|
-
|
|
18134
|
+
if (inner.preamble) {
|
|
18135
|
+
const leafLoopParams = outerLoopParam ? [
|
|
18136
|
+
{ param: outerLoopParam, bindings: outerLoopParamBindings },
|
|
18137
|
+
{ param: inner.param, bindings: inner.paramBindings }
|
|
18138
|
+
] : [{ param: inner.param, bindings: inner.paramBindings }];
|
|
18139
|
+
preludeStatements.push(renderPreamble(inner.preamble, {
|
|
18140
|
+
transformJs: (t) => wrapInner(wrapOuter(t)),
|
|
18141
|
+
renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, leafLoopParams, undefined, true)
|
|
18142
|
+
}));
|
|
18143
|
+
}
|
|
17472
18144
|
const childRefs = buildChildRefBindings(inner.bindings.refs, inner.param, inner.paramBindings);
|
|
17473
18145
|
return {
|
|
17474
18146
|
mode: "reactive",
|
|
@@ -17490,8 +18162,11 @@ function buildStaticEmit(inner, level, uidSuffix) {
|
|
|
17490
18162
|
const indexAlias = nestedLoopIndexAlias(inner, `__innerIdx${uidSuffix}`, inner.param, level.comps, level.events);
|
|
17491
18163
|
if (indexAlias)
|
|
17492
18164
|
preludeStatements.push(indexAlias);
|
|
17493
|
-
if (inner.
|
|
17494
|
-
preludeStatements.push(inner.
|
|
18165
|
+
if (inner.preamble) {
|
|
18166
|
+
preludeStatements.push(renderPreamble(inner.preamble, {
|
|
18167
|
+
renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, undefined, undefined, true)
|
|
18168
|
+
}));
|
|
18169
|
+
}
|
|
17495
18170
|
return {
|
|
17496
18171
|
mode: "static",
|
|
17497
18172
|
rawKey: inner.key ?? null,
|
|
@@ -17511,6 +18186,7 @@ function buildTopLevelCompositePlan(elem, profileComponentName) {
|
|
|
17511
18186
|
const outerCompsByDepth = nestedComps.filter((c) => !c.loopDepth || c.loopDepth === 0);
|
|
17512
18187
|
return {
|
|
17513
18188
|
kind: "composite",
|
|
18189
|
+
rowConstruction: "string-template",
|
|
17514
18190
|
containerVar: `_${varSlotId(elem.slotId)}`,
|
|
17515
18191
|
markerId: elem.markerId,
|
|
17516
18192
|
arrayExpr: buildChainedArrayExpr(elem),
|
|
@@ -17518,7 +18194,10 @@ function buildTopLevelCompositePlan(elem, profileComponentName) {
|
|
|
17518
18194
|
paramHead,
|
|
17519
18195
|
paramUnwrap,
|
|
17520
18196
|
indexParam: elem.index || "__idx",
|
|
17521
|
-
mapPreambleWrapped: elem.
|
|
18197
|
+
mapPreambleWrapped: elem.preamble ? renderPreamble(elem.preamble, {
|
|
18198
|
+
transformJs: wrap,
|
|
18199
|
+
renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: elem.param, bindings: elem.paramBindings }], undefined, true)
|
|
18200
|
+
}) : "",
|
|
17522
18201
|
template: elem.template,
|
|
17523
18202
|
outerComps: filterCondCompsOut(outerCompsByDepth, elem.bindings.conditionals),
|
|
17524
18203
|
outerEvents: elem.bindings.events.filter((ev) => ev.nestedLoops.length === 0),
|
|
@@ -17557,6 +18236,7 @@ function buildBranchCompositePlan(loop, cv, profileComponentName) {
|
|
|
17557
18236
|
const outerCompsByDepth = nestedComps.filter((c) => !c.loopDepth || c.loopDepth === 0);
|
|
17558
18237
|
return {
|
|
17559
18238
|
kind: "composite",
|
|
18239
|
+
rowConstruction: "string-template",
|
|
17560
18240
|
containerVar: `__loop_${cv}`,
|
|
17561
18241
|
markerId: loop.markerId,
|
|
17562
18242
|
arrayExpr: buildChainedArrayExpr(loop),
|
|
@@ -17564,7 +18244,10 @@ function buildBranchCompositePlan(loop, cv, profileComponentName) {
|
|
|
17564
18244
|
paramHead,
|
|
17565
18245
|
paramUnwrap,
|
|
17566
18246
|
indexParam: loop.index || "__idx",
|
|
17567
|
-
mapPreambleWrapped: loop.
|
|
18247
|
+
mapPreambleWrapped: loop.preamble ? renderPreamble(loop.preamble, {
|
|
18248
|
+
transformJs: wrap,
|
|
18249
|
+
renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: loop.param, bindings: loop.paramBindings }], undefined, true)
|
|
18250
|
+
}) : "",
|
|
17568
18251
|
template: loop.template,
|
|
17569
18252
|
outerComps: filterCondCompsOut(outerCompsByDepth, loop.bindings.conditionals),
|
|
17570
18253
|
outerEvents: childEvents.filter((ev) => ev.nestedLoops.length === 0),
|
|
@@ -17627,7 +18310,10 @@ function buildDynamicLoopDelegationPlan(elem, profileComponentName) {
|
|
|
17627
18310
|
paramBindings: elem.paramBindings,
|
|
17628
18311
|
key: elem.key,
|
|
17629
18312
|
index: elem.index,
|
|
17630
|
-
mapPreamble: elem.
|
|
18313
|
+
mapPreamble: elem.preamble ? renderPreamble(elem.preamble, {
|
|
18314
|
+
renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, undefined, undefined, true)
|
|
18315
|
+
}) : null,
|
|
18316
|
+
mapPreambleDeclaredNames: elem.preamble?.declaredNames ?? []
|
|
17631
18317
|
})
|
|
17632
18318
|
};
|
|
17633
18319
|
}
|
|
@@ -17643,7 +18329,10 @@ function buildBranchLoopDelegationPlan(loop, cv, profileComponentName) {
|
|
|
17643
18329
|
paramBindings: loop.paramBindings,
|
|
17644
18330
|
key: loop.key,
|
|
17645
18331
|
index: loop.index,
|
|
17646
|
-
mapPreamble: loop.
|
|
18332
|
+
mapPreamble: loop.preamble ? renderPreamble(loop.preamble, {
|
|
18333
|
+
renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, undefined, undefined, true)
|
|
18334
|
+
}) : null,
|
|
18335
|
+
mapPreambleDeclaredNames: loop.preamble?.declaredNames ?? []
|
|
17647
18336
|
})
|
|
17648
18337
|
};
|
|
17649
18338
|
}
|
|
@@ -17657,7 +18346,10 @@ function buildStaticArrayDelegationPlan(elem, profileComponentName) {
|
|
|
17657
18346
|
kind: "static-index",
|
|
17658
18347
|
arrayExpr: buildChainedArrayExpr(elem),
|
|
17659
18348
|
param: elem.param,
|
|
17660
|
-
mapPreamble: elem.
|
|
18349
|
+
mapPreamble: elem.preamble ? renderPreamble(elem.preamble, {
|
|
18350
|
+
renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, undefined, undefined, true)
|
|
18351
|
+
}) : null,
|
|
18352
|
+
mapPreambleDeclaredNames: elem.preamble?.declaredNames ?? [],
|
|
17661
18353
|
offset: elem.offset ?? null,
|
|
17662
18354
|
indexParam: elem.index ?? null
|
|
17663
18355
|
}
|
|
@@ -17674,6 +18366,7 @@ function buildKeyedOrIndexLookup(args) {
|
|
|
17674
18366
|
paramBindings: args.paramBindings,
|
|
17675
18367
|
keyWithItem,
|
|
17676
18368
|
mapPreamble: args.mapPreamble,
|
|
18369
|
+
mapPreambleDeclaredNames: args.mapPreambleDeclaredNames,
|
|
17677
18370
|
hasBindings,
|
|
17678
18371
|
indexParam: args.index
|
|
17679
18372
|
};
|
|
@@ -17683,6 +18376,7 @@ function buildKeyedOrIndexLookup(args) {
|
|
|
17683
18376
|
arrayExpr: args.array,
|
|
17684
18377
|
param: args.param,
|
|
17685
18378
|
mapPreamble: args.mapPreamble,
|
|
18379
|
+
mapPreambleDeclaredNames: args.mapPreambleDeclaredNames,
|
|
17686
18380
|
hasBindings,
|
|
17687
18381
|
indexParam: args.index
|
|
17688
18382
|
};
|
|
@@ -17704,17 +18398,23 @@ function buildBranchLoopPlan(loop, profileComponentName) {
|
|
|
17704
18398
|
}
|
|
17705
18399
|
const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(loop.param, loop.paramBindings);
|
|
17706
18400
|
const hasReactiveEffects = loop.bindings.reactiveAttrs.length > 0 || loop.bindings.reactiveTexts.length > 0 || loop.bindings.conditionals.length > 0;
|
|
18401
|
+
const fm = loop.flatMapClient;
|
|
17707
18402
|
const plan = {
|
|
17708
18403
|
kind: "plain",
|
|
18404
|
+
rowConstruction: "string-template",
|
|
17709
18405
|
containerSlotId,
|
|
17710
18406
|
containerVar,
|
|
17711
18407
|
markerId: loop.markerId,
|
|
17712
|
-
|
|
17713
|
-
|
|
18408
|
+
flatMapLeafItem: fm ? true : undefined,
|
|
18409
|
+
arrayExpr: fm ? `(${buildChainedArrayExpr(loop)}).flatMap(${fm.params} => ${fm.body})` : buildChainedArrayExpr(loop),
|
|
18410
|
+
keyFn: fm ? fm.keyed ? "(__bfD, __bfI) => String(__bfD.k ?? __bfI)" : "null" : loopKeyFn(loop),
|
|
17714
18411
|
paramHead,
|
|
17715
18412
|
paramUnwrap,
|
|
17716
18413
|
indexParam: loop.index || "__idx",
|
|
17717
|
-
mapPreambleWrapped: loop.
|
|
18414
|
+
mapPreambleWrapped: loop.preamble ? renderPreamble(loop.preamble, {
|
|
18415
|
+
transformJs: (t) => wrapLoopParamAsAccessor(t, loop.param, loop.paramBindings),
|
|
18416
|
+
renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: loop.param, bindings: loop.paramBindings }], undefined, true)
|
|
18417
|
+
}) : "",
|
|
17718
18418
|
template: loop.template,
|
|
17719
18419
|
reactiveEffects: hasReactiveEffects ? buildReactiveEffectsPlan({
|
|
17720
18420
|
attrs: loop.bindings.reactiveAttrs,
|
|
@@ -17726,6 +18426,7 @@ function buildBranchLoopPlan(loop, profileComponentName) {
|
|
|
17726
18426
|
}) : null,
|
|
17727
18427
|
eventDelegation: buildBranchLoopDelegationPlan(loop, cv, profileComponentName),
|
|
17728
18428
|
childRefs: buildChildRefBindings(loop.bindings.refs, loop.param, loop.paramBindings),
|
|
18429
|
+
preambleRegions: buildPreambleRegionPlans(loop.preambleRegions, loop.param, loop.paramBindings),
|
|
17729
18430
|
bodyIsMultiRoot: loop.bodyIsMultiRoot ?? false,
|
|
17730
18431
|
profileLoopId: profileComponentName ? `${profileComponentName}#binding:${containerSlotId}` : undefined
|
|
17731
18432
|
};
|
|
@@ -18373,6 +19074,22 @@ function emitLoopChildRefs(lines, refs, opts) {
|
|
|
18373
19074
|
lines.push(`${indent}if (${varName}) ${emitRefCall(ref.callback, varName)} }`);
|
|
18374
19075
|
}
|
|
18375
19076
|
}
|
|
19077
|
+
function emitPreambleRegionEffects(lines, regions, mapPreambleWrapped, opts) {
|
|
19078
|
+
if (regions.length === 0)
|
|
19079
|
+
return;
|
|
19080
|
+
const { indent, elVar } = opts;
|
|
19081
|
+
for (const region of regions) {
|
|
19082
|
+
const v = varSlotId(region.slotId);
|
|
19083
|
+
lines.push(`${indent}{ let __last_${v}`);
|
|
19084
|
+
lines.push(`${indent}createEffect(() => {`);
|
|
19085
|
+
if (mapPreambleWrapped)
|
|
19086
|
+
lines.push(`${indent} ${mapPreambleWrapped}`);
|
|
19087
|
+
lines.push(`${indent} const __html_${v} = ${region.valueExpr}`);
|
|
19088
|
+
lines.push(`${indent} if (__last_${v} === undefined) { __last_${v} = __html_${v}; return }`);
|
|
19089
|
+
lines.push(`${indent} if (__html_${v} !== __last_${v}) { __last_${v} = __html_${v}; patchSlotRange(${elVar}, '${region.slotId}', __html_${v}) }`);
|
|
19090
|
+
lines.push(`${indent}}) }`);
|
|
19091
|
+
}
|
|
19092
|
+
}
|
|
18376
19093
|
function stringifyLoop(lines, plan) {
|
|
18377
19094
|
switch (plan.kind) {
|
|
18378
19095
|
case "static":
|
|
@@ -18414,8 +19131,24 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
|
|
|
18414
19131
|
childRefs,
|
|
18415
19132
|
bodyIsMultiRoot,
|
|
18416
19133
|
anchored,
|
|
18417
|
-
anchorKeyExpr
|
|
19134
|
+
anchorKeyExpr,
|
|
19135
|
+
preambleRegions
|
|
18418
19136
|
} = plan;
|
|
19137
|
+
if (plan.flatMapLeafItem) {
|
|
19138
|
+
const loopBfIdArg = plan.profileLoopId ? `, ${JSON.stringify(plan.profileLoopId)}` : "";
|
|
19139
|
+
lines.push(`${topIndent}mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (__bfD, ${indexParam}, __existing) => {`);
|
|
19140
|
+
lines.push(`${topIndent} let __el = __existing`);
|
|
19141
|
+
lines.push(`${topIndent} if (!__el) { const __tpl = document.createElement('template'); __tpl.innerHTML = __bfD().h; __el = __tpl.content.firstElementChild }`);
|
|
19142
|
+
lines.push(`${topIndent} let __last = __existing ? undefined : __bfD().h`);
|
|
19143
|
+
lines.push(`${topIndent} createEffect(() => {`);
|
|
19144
|
+
lines.push(`${topIndent} const __html = __bfD().h`);
|
|
19145
|
+
lines.push(`${topIndent} if (__last === undefined) { __last = __html; return }`);
|
|
19146
|
+
lines.push(`${topIndent} if (__html !== __last) { __last = __html; patchLeaf(__el, __html) }`);
|
|
19147
|
+
lines.push(`${topIndent} })`);
|
|
19148
|
+
lines.push(`${topIndent} return __el`);
|
|
19149
|
+
lines.push(`${topIndent}}, '${markerId}'${loopBfIdArg})`);
|
|
19150
|
+
return;
|
|
19151
|
+
}
|
|
18419
19152
|
if (anchored) {
|
|
18420
19153
|
stringifyAnchoredLoop(lines, plan, topIndent, anchorKeyExpr);
|
|
18421
19154
|
return;
|
|
@@ -18426,7 +19159,7 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
|
|
|
18426
19159
|
emitHoistedTemplateDecl(lines, topIndent, tplVar, hoistedTpl);
|
|
18427
19160
|
}
|
|
18428
19161
|
const loopBfId = plan.profileLoopId ? `, ${JSON.stringify(plan.profileLoopId)}` : "";
|
|
18429
|
-
if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0) {
|
|
19162
|
+
if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0 && preambleRegions.length === 0) {
|
|
18430
19163
|
const unwrapInline = paramUnwrap ? `${paramUnwrap} ` : "";
|
|
18431
19164
|
const preamble = mapPreambleWrapped ? `${mapPreambleWrapped}; ` : "";
|
|
18432
19165
|
const cloneExpr = hoistedTpl ? `return ${hoistedCloneExpr(tplVar, hoistedTpl)}` : emitTemplateCloneInline(template);
|
|
@@ -18474,6 +19207,7 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
|
|
|
18474
19207
|
});
|
|
18475
19208
|
}
|
|
18476
19209
|
emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot, elementIndexBySlot: pathPlan?.elementIndexBySlot });
|
|
19210
|
+
emitPreambleRegionEffects(lines, preambleRegions, mapPreambleWrapped, { indent: bodyIndent, elVar: "__el" });
|
|
18477
19211
|
lines.push(`${bodyIndent}return __el`);
|
|
18478
19212
|
lines.push(`${topIndent}}, '${markerId}'${loopBfId})`);
|
|
18479
19213
|
}
|
|
@@ -18784,6 +19518,12 @@ function indexBindingLine(handler, indexParam, indexExpr) {
|
|
|
18784
19518
|
return null;
|
|
18785
19519
|
return `const ${indexParam} = ${indexExpr}`;
|
|
18786
19520
|
}
|
|
19521
|
+
function preambleLineForHandler(mapPreamble, declaredNames, handler) {
|
|
19522
|
+
if (!mapPreamble || declaredNames.length === 0)
|
|
19523
|
+
return null;
|
|
19524
|
+
const free = extractFreeIdentifiersFromText(handler);
|
|
19525
|
+
return declaredNames.some((name) => free.has(name)) ? mapPreamble : null;
|
|
19526
|
+
}
|
|
18787
19527
|
function stringifyEventDelegation(lines, plan) {
|
|
18788
19528
|
const { containerVar, events, itemLookup, profileComponentName } = plan;
|
|
18789
19529
|
const eventsByName = new Map;
|
|
@@ -18804,7 +19544,7 @@ function stringifyEventDelegation(lines, plan) {
|
|
|
18804
19544
|
for (const ev of evs) {
|
|
18805
19545
|
const childVar = varSlotId(ev.childSlotId);
|
|
18806
19546
|
lines.push(` const ${childVar}El = target.closest('[bf="${ev.childSlotId}"]')`);
|
|
18807
|
-
lines.push(` if (${childVar}El) {`);
|
|
19547
|
+
lines.push(` if (${childVar}El && ${containerVar}.contains(${childVar}El)) {`);
|
|
18808
19548
|
const handlerCall = withTurn(`(${ev.handler.trim()})(__bfEvt)`, profileComponentName, ev.childSlotId, ev.eventName);
|
|
18809
19549
|
switch (itemLookup.kind) {
|
|
18810
19550
|
case "keyed":
|
|
@@ -18829,7 +19569,8 @@ function stringifyEventDelegation(lines, plan) {
|
|
|
18829
19569
|
}
|
|
18830
19570
|
}
|
|
18831
19571
|
function emitKeyedLookup(ls, ev, handlerCall, lookup) {
|
|
18832
|
-
const { arrayExpr, param, keyWithItem, mapPreamble, hasBindings, indexParam } = lookup;
|
|
19572
|
+
const { arrayExpr, param, keyWithItem, mapPreamble, mapPreambleDeclaredNames, hasBindings, indexParam } = lookup;
|
|
19573
|
+
const preambleLine = preambleLineForHandler(mapPreamble, mapPreambleDeclaredNames, ev.handler);
|
|
18833
19574
|
if (ev.nestedLoops.length === 0) {
|
|
18834
19575
|
const idxLine2 = indexBindingLine(ev.handler, indexParam, `${arrayExpr}.findIndex(item => String(${keyWithItem}) === key)`);
|
|
18835
19576
|
ls.push(` const li = ${varSlotId(ev.childSlotId)}El.closest('[${DATA_KEY}]')`);
|
|
@@ -18839,20 +19580,21 @@ function emitKeyedLookup(ls, ev, handlerCall, lookup) {
|
|
|
18839
19580
|
ls.push(` const __bfLoopItem = ${arrayExpr}.find(item => String(${keyWithItem}) === key)`);
|
|
18840
19581
|
ls.push(` if (__bfLoopItem) {`);
|
|
18841
19582
|
ls.push(` const ${param} = __bfLoopItem`);
|
|
18842
|
-
if (
|
|
18843
|
-
ls.push(` ${
|
|
19583
|
+
if (preambleLine)
|
|
19584
|
+
ls.push(` ${preambleLine}`);
|
|
18844
19585
|
if (idxLine2)
|
|
18845
19586
|
ls.push(` ${idxLine2}`);
|
|
18846
|
-
ls.push(`
|
|
19587
|
+
ls.push(` ;${handlerCall}`);
|
|
18847
19588
|
ls.push(` }`);
|
|
18848
19589
|
} else {
|
|
18849
19590
|
ls.push(` const ${param} = ${arrayExpr}.find(item => String(${keyWithItem}) === key)`);
|
|
18850
|
-
if (
|
|
18851
|
-
|
|
19591
|
+
ls.push(` if (${param}) {`);
|
|
19592
|
+
if (preambleLine)
|
|
19593
|
+
ls.push(` ${preambleLine}`);
|
|
18852
19594
|
if (idxLine2)
|
|
18853
|
-
ls.push(`
|
|
18854
|
-
|
|
18855
|
-
|
|
19595
|
+
ls.push(` ${idxLine2}`);
|
|
19596
|
+
ls.push(` ;${handlerCall}`);
|
|
19597
|
+
ls.push(` }`);
|
|
18856
19598
|
}
|
|
18857
19599
|
ls.push(` }`);
|
|
18858
19600
|
return;
|
|
@@ -18879,16 +19621,18 @@ function emitKeyedLookup(ls, ev, handlerCall, lookup) {
|
|
|
18879
19621
|
}
|
|
18880
19622
|
const outerGuard = hasBindings ? "__bfLoopItem" : param;
|
|
18881
19623
|
const allParams = [outerGuard, ...ev.nestedLoops.map((n) => n.param)];
|
|
18882
|
-
if (mapPreamble)
|
|
18883
|
-
ls.push(` ${mapPreamble}`);
|
|
18884
19624
|
const idxLine = indexBindingLine(ev.handler, indexParam, `${arrayExpr}.findIndex(item => String(${keyWithItem}) === outerKey)`);
|
|
19625
|
+
ls.push(` if (${allParams.join(" && ")}) {`);
|
|
19626
|
+
if (preambleLine)
|
|
19627
|
+
ls.push(` ${preambleLine}`);
|
|
18885
19628
|
if (idxLine)
|
|
18886
|
-
ls.push(`
|
|
18887
|
-
|
|
18888
|
-
|
|
19629
|
+
ls.push(` ${idxLine}`);
|
|
19630
|
+
ls.push(` ;${handlerCall}`);
|
|
19631
|
+
ls.push(` }`);
|
|
18889
19632
|
}
|
|
18890
19633
|
function emitDynamicIndexLookup(ls, ev, handlerCall, lookup) {
|
|
18891
|
-
const { arrayExpr, param, mapPreamble, hasBindings, indexParam } = lookup;
|
|
19634
|
+
const { arrayExpr, param, mapPreamble, mapPreambleDeclaredNames, hasBindings, indexParam } = lookup;
|
|
19635
|
+
const preambleLine = preambleLineForHandler(mapPreamble, mapPreambleDeclaredNames, ev.handler);
|
|
18892
19636
|
const idxLine = indexBindingLine(ev.handler, indexParam, "idx");
|
|
18893
19637
|
ls.push(` const li = ${varSlotId(ev.childSlotId)}El.closest('li, [bf-i]')`);
|
|
18894
19638
|
ls.push(` if (li && li.parentElement) {`);
|
|
@@ -18897,25 +19641,27 @@ function emitDynamicIndexLookup(ls, ev, handlerCall, lookup) {
|
|
|
18897
19641
|
ls.push(` const __bfLoopItem = ${arrayExpr}[idx]`);
|
|
18898
19642
|
ls.push(` if (__bfLoopItem) {`);
|
|
18899
19643
|
ls.push(` const ${param} = __bfLoopItem`);
|
|
18900
|
-
if (
|
|
18901
|
-
ls.push(` ${
|
|
19644
|
+
if (preambleLine)
|
|
19645
|
+
ls.push(` ${preambleLine}`);
|
|
18902
19646
|
if (idxLine)
|
|
18903
19647
|
ls.push(` ${idxLine}`);
|
|
18904
|
-
ls.push(`
|
|
19648
|
+
ls.push(` ;${handlerCall}`);
|
|
18905
19649
|
ls.push(` }`);
|
|
18906
19650
|
} else {
|
|
18907
19651
|
ls.push(` const ${param} = ${arrayExpr}[idx]`);
|
|
18908
|
-
if (
|
|
18909
|
-
|
|
19652
|
+
ls.push(` if (${param}) {`);
|
|
19653
|
+
if (preambleLine)
|
|
19654
|
+
ls.push(` ${preambleLine}`);
|
|
18910
19655
|
if (idxLine)
|
|
18911
|
-
ls.push(`
|
|
18912
|
-
|
|
18913
|
-
|
|
19656
|
+
ls.push(` ${idxLine}`);
|
|
19657
|
+
ls.push(` ;${handlerCall}`);
|
|
19658
|
+
ls.push(` }`);
|
|
18914
19659
|
}
|
|
18915
19660
|
ls.push(` }`);
|
|
18916
19661
|
}
|
|
18917
19662
|
function emitStaticIndexLookup(ls, ev, handlerCall, lookup, containerVar) {
|
|
18918
|
-
const { arrayExpr, param, mapPreamble, offset, indexParam } = lookup;
|
|
19663
|
+
const { arrayExpr, param, mapPreamble, mapPreambleDeclaredNames, offset, indexParam } = lookup;
|
|
19664
|
+
const preambleLine = preambleLineForHandler(mapPreamble, mapPreambleDeclaredNames, ev.handler);
|
|
18919
19665
|
const idxLine = indexBindingLine(ev.handler, indexParam, "__idx");
|
|
18920
19666
|
ls.push(` let __el = ${varSlotId(ev.childSlotId)}El`);
|
|
18921
19667
|
ls.push(` while (__el.parentElement && __el.parentElement !== ${containerVar}) __el = __el.parentElement`);
|
|
@@ -18923,12 +19669,13 @@ function emitStaticIndexLookup(ls, ev, handlerCall, lookup, containerVar) {
|
|
|
18923
19669
|
const idxOffset = buildLoopChildIndexSubtraction(offset ?? undefined);
|
|
18924
19670
|
ls.push(` const __idx = Array.from(${containerVar}.children).indexOf(__el)${idxOffset}`);
|
|
18925
19671
|
ls.push(` const ${param} = ${arrayExpr}[__idx]`);
|
|
18926
|
-
if (
|
|
18927
|
-
|
|
19672
|
+
ls.push(` if (${param}) {`);
|
|
19673
|
+
if (preambleLine)
|
|
19674
|
+
ls.push(` ${preambleLine}`);
|
|
18928
19675
|
if (idxLine)
|
|
18929
|
-
ls.push(`
|
|
18930
|
-
|
|
18931
|
-
|
|
19676
|
+
ls.push(` ${idxLine}`);
|
|
19677
|
+
ls.push(` ;${handlerCall}`);
|
|
19678
|
+
ls.push(` }`);
|
|
18932
19679
|
ls.push(` }`);
|
|
18933
19680
|
}
|
|
18934
19681
|
|
|
@@ -18961,12 +19708,29 @@ function emitPlain(lines, plan) {
|
|
|
18961
19708
|
eventDelegation,
|
|
18962
19709
|
childRefs,
|
|
18963
19710
|
bodyIsMultiRoot,
|
|
18964
|
-
profileLoopId
|
|
19711
|
+
profileLoopId,
|
|
19712
|
+
preambleRegions
|
|
18965
19713
|
} = plan;
|
|
18966
19714
|
const loopBfId = profileLoopId ? `, ${JSON.stringify(profileLoopId)}` : "";
|
|
18967
19715
|
const unwrapInline = paramUnwrap ? `${paramUnwrap} ` : "";
|
|
18968
19716
|
lines.push(` __disposers.push(createDisposableEffect(() => {`);
|
|
18969
|
-
if (
|
|
19717
|
+
if (plan.flatMapLeafItem) {
|
|
19718
|
+
lines.push(` if (${containerVar}) mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (__bfD, ${indexParam}, __existing) => {`);
|
|
19719
|
+
lines.push(` let __el = __existing`);
|
|
19720
|
+
lines.push(` if (!__el) { const __tpl = document.createElement('template'); __tpl.innerHTML = __bfD().h; __el = __tpl.content.firstElementChild }`);
|
|
19721
|
+
lines.push(` let __last = __existing ? undefined : __bfD().h`);
|
|
19722
|
+
lines.push(` createEffect(() => {`);
|
|
19723
|
+
lines.push(` const __html = __bfD().h`);
|
|
19724
|
+
lines.push(` if (__last === undefined) { __last = __html; return }`);
|
|
19725
|
+
lines.push(` if (__html !== __last) { __last = __html; patchLeaf(__el, __html) }`);
|
|
19726
|
+
lines.push(` })`);
|
|
19727
|
+
lines.push(` return __el`);
|
|
19728
|
+
lines.push(` }, '${markerId}'${loopBfId})`);
|
|
19729
|
+
lines.push(` }))`);
|
|
19730
|
+
stringifyEventDelegation(lines, eventDelegation);
|
|
19731
|
+
return;
|
|
19732
|
+
}
|
|
19733
|
+
if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0 && preambleRegions.length === 0) {
|
|
18970
19734
|
const cloneExpr = emitTemplateCloneInline(template);
|
|
18971
19735
|
if (mapPreambleWrapped) {
|
|
18972
19736
|
lines.push(` if (${containerVar}) mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}if (__existing) return __existing; ${mapPreambleWrapped}; ${cloneExpr} }, '${markerId}'${loopBfId})`);
|
|
@@ -18991,6 +19755,7 @@ function emitPlain(lines, plan) {
|
|
|
18991
19755
|
stringifyReactiveEffects(lines, reactiveEffects, { indent: " ", elVar: "__el", bodyIsMultiRoot });
|
|
18992
19756
|
}
|
|
18993
19757
|
emitLoopChildRefs(lines, childRefs, { indent: " ", elVar: "__el", bodyIsMultiRoot });
|
|
19758
|
+
emitPreambleRegionEffects(lines, preambleRegions, mapPreambleWrapped, { indent: " ", elVar: "__el" });
|
|
18994
19759
|
lines.push(` return __el`);
|
|
18995
19760
|
lines.push(` }, '${markerId}'${loopBfId})`);
|
|
18996
19761
|
}
|
|
@@ -19130,6 +19895,7 @@ function buildComponentLoopPlan(elem, profileComponentName) {
|
|
|
19130
19895
|
const hasChildConds = elem.bindings.conditionals.length > 0;
|
|
19131
19896
|
return {
|
|
19132
19897
|
kind: "component",
|
|
19898
|
+
rowConstruction: "dom-ops",
|
|
19133
19899
|
containerVar: `_${varSlotId(elem.slotId)}`,
|
|
19134
19900
|
markerId: elem.markerId,
|
|
19135
19901
|
arrayExpr: buildChainedArrayExpr(elem),
|
|
@@ -19175,8 +19941,32 @@ function buildPlainLoopPlan(elem, profileComponentName) {
|
|
|
19175
19941
|
const wrap = (expr) => wrapLoopParamAsAccessor(expr, elem.param, elem.paramBindings);
|
|
19176
19942
|
const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(elem.param, elem.paramBindings);
|
|
19177
19943
|
const hasReactive2 = elem.bindings.reactiveAttrs.length > 0 || elem.bindings.reactiveTexts.length > 0 || elem.bindings.conditionals.length > 0;
|
|
19944
|
+
if (elem.flatMapClient) {
|
|
19945
|
+
return {
|
|
19946
|
+
kind: "plain",
|
|
19947
|
+
rowConstruction: "string-template",
|
|
19948
|
+
containerVar: `_${varSlotId(elem.slotId)}`,
|
|
19949
|
+
markerId: elem.markerId,
|
|
19950
|
+
profileLoopId: profileComponentName ? `${profileComponentName}#binding:${elem.slotId}` : undefined,
|
|
19951
|
+
arrayExpr: `(${buildChainedArrayExpr(elem)}).flatMap(${elem.flatMapClient.params} => ${elem.flatMapClient.body})`,
|
|
19952
|
+
keyFn: elem.flatMapClient.keyed ? "(__bfD, __bfI) => String(__bfD.k ?? __bfI)" : "null",
|
|
19953
|
+
paramHead: "__bfD",
|
|
19954
|
+
paramUnwrap: "",
|
|
19955
|
+
indexParam: "__idx",
|
|
19956
|
+
mapPreambleWrapped: "",
|
|
19957
|
+
template: "",
|
|
19958
|
+
reactiveEffects: null,
|
|
19959
|
+
childRefs: [],
|
|
19960
|
+
bodyIsMultiRoot: false,
|
|
19961
|
+
anchored: false,
|
|
19962
|
+
anchorKeyExpr: "__idx",
|
|
19963
|
+
flatMapLeafItem: true,
|
|
19964
|
+
preambleRegions: []
|
|
19965
|
+
};
|
|
19966
|
+
}
|
|
19178
19967
|
return {
|
|
19179
19968
|
kind: "plain",
|
|
19969
|
+
rowConstruction: "string-template",
|
|
19180
19970
|
containerVar: `_${varSlotId(elem.slotId)}`,
|
|
19181
19971
|
markerId: elem.markerId,
|
|
19182
19972
|
profileLoopId: profileComponentName ? `${profileComponentName}#binding:${elem.slotId}` : undefined,
|
|
@@ -19185,12 +19975,16 @@ function buildPlainLoopPlan(elem, profileComponentName) {
|
|
|
19185
19975
|
paramHead,
|
|
19186
19976
|
paramUnwrap,
|
|
19187
19977
|
indexParam: elem.index || "__idx",
|
|
19188
|
-
mapPreambleWrapped: elem.
|
|
19978
|
+
mapPreambleWrapped: elem.preamble ? renderPreamble(elem.preamble, {
|
|
19979
|
+
transformJs: wrap,
|
|
19980
|
+
renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: elem.param, bindings: elem.paramBindings }], undefined, true)
|
|
19981
|
+
}) : "",
|
|
19189
19982
|
template: elem.template,
|
|
19190
19983
|
skeletonTemplate: elem.skeletonTemplate,
|
|
19191
19984
|
skeletonPaths: elem.skeletonPaths,
|
|
19192
19985
|
reactiveEffects: hasReactive2 ? buildLoopReactiveEffectsPlan(elem, profileComponentName) : null,
|
|
19193
19986
|
childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings),
|
|
19987
|
+
preambleRegions: buildPreambleRegionPlans(elem.preambleRegions, elem.param, elem.paramBindings),
|
|
19194
19988
|
bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false,
|
|
19195
19989
|
anchored: elem.bodyIsItemConditional ?? false,
|
|
19196
19990
|
anchorKeyExpr: elem.key ? wrap(elem.key) : elem.index || "__idx"
|
|
@@ -19212,6 +20006,7 @@ function buildStaticLoopPlan(elem, unsafeLocalNames, profileComponentName) {
|
|
|
19212
20006
|
const childIndexExpr = buildLoopChildIndexExpr(indexParam, elem.offset);
|
|
19213
20007
|
return {
|
|
19214
20008
|
kind: "static",
|
|
20009
|
+
rowConstruction: "string-template",
|
|
19215
20010
|
containerVar: `_${varSlotId(elem.slotId)}`,
|
|
19216
20011
|
arrayExpr: elem.array,
|
|
19217
20012
|
param: elem.param,
|
|
@@ -19233,7 +20028,9 @@ function buildStaticLoopMaterialize(elem, unsafeLocalNames) {
|
|
|
19233
20028
|
return null;
|
|
19234
20029
|
return {
|
|
19235
20030
|
itemTemplate: elem.staticItemTemplate,
|
|
19236
|
-
mapPreamble: elem.
|
|
20031
|
+
mapPreamble: elem.preamble ? renderPreamble(elem.preamble, {
|
|
20032
|
+
renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: elem.param, bindings: elem.paramBindings }], undefined, true)
|
|
20033
|
+
}) : "",
|
|
19237
20034
|
bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false
|
|
19238
20035
|
};
|
|
19239
20036
|
}
|
|
@@ -19262,6 +20059,7 @@ function emitLoopUpdates(lines, ctx, unsafeLocalNames) {
|
|
|
19262
20059
|
unsafeLocalNames,
|
|
19263
20060
|
profileComponentName: ctx.profile ? ctx.componentName : undefined
|
|
19264
20061
|
});
|
|
20062
|
+
internalInvariant(!(elem.preamble && elem.preamble.builderNames.length > 0 && plan.rowConstruction === "dom-ops"), `loop variant '${plan.kind}' declares dom-ops row construction but received a JSX-bearing preamble — add a Phase-1 refusal (or wire renderPreamble support) for this shape`);
|
|
19265
20063
|
stringifyLoop(lines, plan);
|
|
19266
20064
|
emitLoopEventDelegation(lines, elem, plan.kind, ctx.profile ? ctx.componentName : undefined);
|
|
19267
20065
|
}
|
|
@@ -20192,19 +20990,19 @@ function isJsxLike(expr) {
|
|
|
20192
20990
|
function collectArrowParamNames(arrow) {
|
|
20193
20991
|
const names = new Set;
|
|
20194
20992
|
for (const p of arrow.parameters)
|
|
20195
|
-
|
|
20993
|
+
collectBindingNames2(p.name, names);
|
|
20196
20994
|
return names;
|
|
20197
20995
|
}
|
|
20198
|
-
function
|
|
20996
|
+
function collectBindingNames2(name, out) {
|
|
20199
20997
|
const push = Array.isArray(out) ? (n) => out.push(n) : (n) => out.add(n);
|
|
20200
20998
|
if (ts16.isIdentifier(name)) {
|
|
20201
20999
|
push(name.text);
|
|
20202
21000
|
} else if (ts16.isObjectBindingPattern(name)) {
|
|
20203
|
-
name.elements.forEach((el) =>
|
|
21001
|
+
name.elements.forEach((el) => collectBindingNames2(el.name, out));
|
|
20204
21002
|
} else if (ts16.isArrayBindingPattern(name)) {
|
|
20205
21003
|
name.elements.forEach((el) => {
|
|
20206
21004
|
if (!ts16.isOmittedExpression(el))
|
|
20207
|
-
|
|
21005
|
+
collectBindingNames2(el.name, out);
|
|
20208
21006
|
});
|
|
20209
21007
|
}
|
|
20210
21008
|
}
|
|
@@ -20213,12 +21011,12 @@ function collectFreeIdentifiers(arrow) {
|
|
|
20213
21011
|
const bound = [];
|
|
20214
21012
|
for (const p of arrow.parameters) {
|
|
20215
21013
|
const names = [];
|
|
20216
|
-
|
|
21014
|
+
collectBindingNames2(p.name, names);
|
|
20217
21015
|
bound.push(...names);
|
|
20218
21016
|
}
|
|
20219
21017
|
function pushBindings(name) {
|
|
20220
21018
|
const names = [];
|
|
20221
|
-
|
|
21019
|
+
collectBindingNames2(name, names);
|
|
20222
21020
|
bound.push(...names);
|
|
20223
21021
|
return names;
|
|
20224
21022
|
}
|
|
@@ -20345,7 +21143,7 @@ function collectModuleScopeNames(sourceFile) {
|
|
|
20345
21143
|
names.add(stmt.name.text);
|
|
20346
21144
|
else if (ts16.isVariableStatement(stmt)) {
|
|
20347
21145
|
for (const decl of stmt.declarationList.declarations)
|
|
20348
|
-
|
|
21146
|
+
collectBindingNames2(decl.name, names);
|
|
20349
21147
|
} else if (ts16.isImportDeclaration(stmt) && stmt.importClause) {
|
|
20350
21148
|
const ic = stmt.importClause;
|
|
20351
21149
|
if (ic.name)
|
|
@@ -21364,8 +22162,13 @@ function walkNode(node, meta, bindings, matchers, errors, seen) {
|
|
|
21364
22162
|
for (const child of nested.children)
|
|
21365
22163
|
walkNode(child, meta, loopBindings, matchers, errors, seen);
|
|
21366
22164
|
}
|
|
21367
|
-
for (const
|
|
21368
|
-
|
|
22165
|
+
for (const seg of node.flatMapCallback?.segments ?? []) {
|
|
22166
|
+
if (seg.kind === "jsx")
|
|
22167
|
+
walkNode(seg.ir, meta, loopBindings, matchers, errors, seen);
|
|
22168
|
+
}
|
|
22169
|
+
for (const seg of node.preamble?.segments ?? []) {
|
|
22170
|
+
if (seg.kind === "jsx")
|
|
22171
|
+
walkNode(seg.ir, meta, loopBindings, matchers, errors, seen);
|
|
21369
22172
|
}
|
|
21370
22173
|
break;
|
|
21371
22174
|
}
|
|
@@ -21427,7 +22230,7 @@ function compileMultipleComponents(source, filePath, componentNames, options) {
|
|
|
21427
22230
|
const entries = [];
|
|
21428
22231
|
const program = options.program ?? (needsTypeBasedDetection(source) ? createProgramForFile(source, filePath)?.program : undefined);
|
|
21429
22232
|
for (const componentName of componentNames) {
|
|
21430
|
-
const ctx = analyzeComponent(source, filePath, componentName, program);
|
|
22233
|
+
const ctx = analyzeComponent(source, filePath, componentName, program, adapter.acceptsCallbackBody);
|
|
21431
22234
|
if (!ctx.jsxReturn) {
|
|
21432
22235
|
errors.push(...ctx.errors);
|
|
21433
22236
|
continue;
|
|
@@ -21736,7 +22539,7 @@ function compileJSX(source, filePath, options) {
|
|
|
21736
22539
|
if (componentNames.length > 1) {
|
|
21737
22540
|
return compileMultipleComponents(compileSource, filePath, componentNames, options);
|
|
21738
22541
|
}
|
|
21739
|
-
const ctx = analyzeComponent(compileSource, filePath, undefined, options.program);
|
|
22542
|
+
const ctx = analyzeComponent(compileSource, filePath, undefined, options.program, options.adapter.acceptsCallbackBody);
|
|
21740
22543
|
if (!ctx.jsxReturn) {
|
|
21741
22544
|
errors.push(...ctx.errors);
|
|
21742
22545
|
const exportedModuleSignals = ctx.signals.filter((s2) => s2.isModule && s2.isExported);
|
|
@@ -21936,6 +22739,7 @@ class BaseAdapter {
|
|
|
21936
22739
|
import { BF_SCOPE as BF_SCOPE4, BF_SLOT, BF_COND } from "@barefootjs/shared";
|
|
21937
22740
|
class JsxAdapter extends BaseAdapter {
|
|
21938
22741
|
componentName = "";
|
|
22742
|
+
acceptsCallbackBody = () => true;
|
|
21939
22743
|
formatImportSpecifiers(specifiers) {
|
|
21940
22744
|
const defaultSpec = specifiers.find((s) => s.isDefault);
|
|
21941
22745
|
const namespaceSpec = specifiers.find((s) => s.isNamespace);
|
|
@@ -22281,6 +23085,10 @@ export default ${this.componentName}` : "";
|
|
|
22281
23085
|
const indexParam = loop.index ? `, ${loop.index}` : "";
|
|
22282
23086
|
const children = this.renderChildren(loop.children);
|
|
22283
23087
|
const safeChildren = children.startsWith("{") ? `<>${children}</>` : children;
|
|
23088
|
+
const preamble = loop.preamble?.ssrText;
|
|
23089
|
+
if (preamble) {
|
|
23090
|
+
return `{${loop.array}.map((${loop.param}${indexParam}) => { ${preamble} return ${safeChildren} })}`;
|
|
23091
|
+
}
|
|
22284
23092
|
return `{${loop.array}.map((${loop.param}${indexParam}) => ${safeChildren})}`;
|
|
22285
23093
|
}
|
|
22286
23094
|
renderComponent(comp) {
|
|
@@ -22481,8 +23289,13 @@ function collectLoopBoundNames(ir) {
|
|
|
22481
23289
|
for (const child of nested.children)
|
|
22482
23290
|
visit3(child);
|
|
22483
23291
|
}
|
|
22484
|
-
for (const
|
|
22485
|
-
|
|
23292
|
+
for (const seg of node.flatMapCallback?.segments ?? []) {
|
|
23293
|
+
if (seg.kind === "jsx")
|
|
23294
|
+
visit3(seg.ir);
|
|
23295
|
+
}
|
|
23296
|
+
for (const seg of node.preamble?.segments ?? []) {
|
|
23297
|
+
if (seg.kind === "jsx")
|
|
23298
|
+
visit3(seg.ir);
|
|
22486
23299
|
}
|
|
22487
23300
|
break;
|
|
22488
23301
|
case "conditional":
|
|
@@ -23058,12 +23871,16 @@ function restNamesMisused(loop, names) {
|
|
|
23058
23871
|
check(l.array);
|
|
23059
23872
|
check(l.templateArray);
|
|
23060
23873
|
check(l.key);
|
|
23061
|
-
|
|
23062
|
-
|
|
23874
|
+
if (l.preamble) {
|
|
23875
|
+
check(preambleAnalysisText(l.preamble));
|
|
23876
|
+
check(preambleAnalysisTemplateText(l.preamble));
|
|
23877
|
+
}
|
|
23063
23878
|
if (l.flatMapCallback) {
|
|
23064
|
-
check(l.flatMapCallback
|
|
23065
|
-
|
|
23066
|
-
|
|
23879
|
+
check(preambleAnalysisText(l.flatMapCallback));
|
|
23880
|
+
for (const seg of l.flatMapCallback.segments) {
|
|
23881
|
+
if (seg.kind === "jsx")
|
|
23882
|
+
visit3(seg.ir);
|
|
23883
|
+
}
|
|
23067
23884
|
}
|
|
23068
23885
|
l.children.forEach(visit3);
|
|
23069
23886
|
};
|