@barefootjs/go-template 0.18.5 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/adapter/analysis/static-child-loop-bake.d.ts +61 -0
  2. package/dist/adapter/analysis/static-child-loop-bake.d.ts.map +1 -0
  3. package/dist/adapter/analysis/static-element-loop-bake.d.ts +83 -0
  4. package/dist/adapter/analysis/static-element-loop-bake.d.ts.map +1 -0
  5. package/dist/adapter/emit-context.d.ts +11 -5
  6. package/dist/adapter/emit-context.d.ts.map +1 -1
  7. package/dist/adapter/go-template-adapter.d.ts +153 -6
  8. package/dist/adapter/go-template-adapter.d.ts.map +1 -1
  9. package/dist/adapter/index.js +514 -53
  10. package/dist/adapter/lib/compile-state.d.ts +24 -0
  11. package/dist/adapter/lib/compile-state.d.ts.map +1 -1
  12. package/dist/adapter/lib/types.d.ts +9 -0
  13. package/dist/adapter/lib/types.d.ts.map +1 -1
  14. package/dist/adapter/props/prop-classes.d.ts +28 -9
  15. package/dist/adapter/props/prop-classes.d.ts.map +1 -1
  16. package/dist/adapter/props/prop-types.d.ts +45 -0
  17. package/dist/adapter/props/prop-types.d.ts.map +1 -1
  18. package/dist/build.js +514 -53
  19. package/dist/conformance-pins.d.ts.map +1 -1
  20. package/dist/index.js +516 -62
  21. package/dist/render-divergences.d.ts.map +1 -1
  22. package/dist/test-render.d.ts.map +1 -1
  23. package/package.json +3 -3
  24. package/src/__tests__/go-template-adapter.test.ts +876 -21
  25. package/src/adapter/analysis/static-child-loop-bake.ts +119 -0
  26. package/src/adapter/analysis/static-element-loop-bake.ts +211 -0
  27. package/src/adapter/emit-context.ts +14 -5
  28. package/src/adapter/go-template-adapter.ts +620 -45
  29. package/src/adapter/lib/compile-state.ts +27 -0
  30. package/src/adapter/lib/types.ts +9 -0
  31. package/src/adapter/props/prop-classes.ts +34 -9
  32. package/src/adapter/props/prop-types.ts +178 -1
  33. package/src/adapter/value/value-lowering.ts +1 -1
  34. package/src/conformance-pins.ts +30 -31
  35. package/src/render-divergences.ts +12 -0
  36. package/src/test-render.ts +127 -10
@@ -24,8 +24,8 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
24
24
  // src/adapter/go-template-adapter.ts
25
25
  import {
26
26
  BaseAdapter,
27
- isBooleanAttr,
28
- parseExpression as parseExpression3,
27
+ isBooleanAttr as isBooleanAttr2,
28
+ parseExpression as parseExpression4,
29
29
  stringifyParsedExpr as stringifyParsedExpr2,
30
30
  parseStyleObjectEntries,
31
31
  isSupported,
@@ -43,7 +43,13 @@ import {
43
43
  prepareLoweringMatchers,
44
44
  envSignalReaderFor,
45
45
  computeSsrSeedPlan,
46
- isStringConcatBinary
46
+ isStringConcatBinary,
47
+ isDangerousInnerHtmlAttr,
48
+ resolveDangerousInnerHtml,
49
+ dangerousInnerHtmlMetacharViolation,
50
+ dangerousInnerHtmlDiagnostic,
51
+ collectLoopBoundNames as collectLoopBoundNames2,
52
+ evaluateStaticLiteral as evaluateStaticLiteral3
47
53
  } from "@barefootjs/jsx";
48
54
  import { findInterpolationEnd } from "@barefootjs/jsx/scanner";
49
55
  import { BF_REGION, escapeHtml } from "@barefootjs/shared";
@@ -349,6 +355,7 @@ class CompileState {
349
355
  restPropsName = null;
350
356
  moduleStringConsts = new Map;
351
357
  localConstants = [];
358
+ staticLoopSourceBoundNames = new Set;
352
359
  localHelperNames = new Set;
353
360
  currentMemos = [];
354
361
  currentTypeDefinitions = [];
@@ -359,6 +366,8 @@ class CompileState {
359
366
  hoistedMemoLocals = new Map;
360
367
  loweringMatchers = [];
361
368
  nillablePropNames = new Set;
369
+ nullishConsumedPropNames = new Set;
370
+ omittableAttrConsumedPropNames = new Set;
362
371
  stringValueNames = new Set;
363
372
  rootScopeNodes = new Set;
364
373
  memoBackedLoopSlice = new Map;
@@ -517,6 +526,178 @@ function collectNestedComponents(node, result) {
517
526
  }
518
527
  }
519
528
 
529
+ // src/adapter/analysis/static-child-loop-bake.ts
530
+ import { evaluateStaticLiteral, parseExpression, resolveStaticLoopSource } from "@barefootjs/jsx";
531
+ function scalarToGoLiteral(value) {
532
+ if (typeof value === "string")
533
+ return `"${escapeGoString(value)}"`;
534
+ if (typeof value === "number")
535
+ return String(value);
536
+ if (typeof value === "boolean")
537
+ return value ? "true" : "false";
538
+ return null;
539
+ }
540
+ function analyzeBakeableStaticChildLoop(nested, localConstants, opts) {
541
+ if (!nested.loopParam || /^[{[]/.test(nested.loopParam))
542
+ return null;
543
+ const staticItemsResult = resolveStaticLoopSource(nested.loopArrayParsed, localConstants, opts);
544
+ if (staticItemsResult === null)
545
+ return null;
546
+ const items = [];
547
+ for (const item of staticItemsResult) {
548
+ const bindings = new Map([[nested.loopParam, item]]);
549
+ const inputFields = [];
550
+ for (const prop of nested.props) {
551
+ if (prop.isEventHandler)
552
+ continue;
553
+ if (prop.name.includes("-"))
554
+ continue;
555
+ const resolved = resolvePropValue(prop.value, bindings);
556
+ if (resolved === undefined)
557
+ return null;
558
+ const goValue = scalarToGoLiteral(resolved);
559
+ if (goValue === null)
560
+ return null;
561
+ inputFields.push({ goField: capitalizeFieldName(prop.name), goValue });
562
+ }
563
+ let dataKey = null;
564
+ if (nested.loopKey) {
565
+ const keyExpr = parseExpression(nested.loopKey);
566
+ const keyResolved = evaluateStaticLiteral(keyExpr, bindings);
567
+ if (keyResolved === null)
568
+ return null;
569
+ dataKey = String(keyResolved.value);
570
+ }
571
+ items.push({ inputFields, dataKey });
572
+ }
573
+ return { items };
574
+ }
575
+ function resolvePropValue(value, bindings) {
576
+ switch (value.kind) {
577
+ case "literal":
578
+ return value.value;
579
+ case "boolean-shorthand":
580
+ case "boolean-attr":
581
+ return true;
582
+ case "expression": {
583
+ if (!value.parsed)
584
+ return;
585
+ const resolved = evaluateStaticLiteral(value.parsed, bindings);
586
+ return resolved === null ? undefined : resolved.value;
587
+ }
588
+ default:
589
+ return;
590
+ }
591
+ }
592
+
593
+ // src/adapter/analysis/static-element-loop-bake.ts
594
+ import {
595
+ evaluateStaticLiteral as evaluateStaticLiteral2,
596
+ resolveStaticLoopSource as resolveStaticLoopSource2
597
+ } from "@barefootjs/jsx";
598
+ var ALLOWED_ATTR_EXPRESSION_KINDS = new Set([
599
+ "identifier",
600
+ "member",
601
+ "index-access",
602
+ "literal"
603
+ ]);
604
+ function analyzeBakeableStaticElementLoop(loop, localConstants, opts) {
605
+ if (loop.childComponent)
606
+ return null;
607
+ if (loop.method === "flatMap" || loop.flatMapCallback)
608
+ return null;
609
+ if (!loop.param || /^[{[]/.test(loop.param))
610
+ return null;
611
+ if (loop.index && loop.index !== "_")
612
+ return null;
613
+ if (loop.paramBindings && loop.paramBindings.length > 0)
614
+ return null;
615
+ if (loop.filterPredicate || loop.sortComparator)
616
+ return null;
617
+ if (loop.iterationShape || loop.objectIteration)
618
+ return null;
619
+ if (loop.bodyIsMultiRoot || loop.bodyIsItemConditional)
620
+ return null;
621
+ if (!isFoldableTree(loop.children))
622
+ return null;
623
+ const items = resolveStaticLoopSource2(loop.arrayParsed, localConstants, opts);
624
+ if (items === null)
625
+ return null;
626
+ for (const item of items) {
627
+ const bindings = new Map([[loop.param, item]]);
628
+ if (!allExpressionsFoldFor(loop.children, bindings))
629
+ return null;
630
+ }
631
+ return { items };
632
+ }
633
+ function isFoldableTree(nodes) {
634
+ for (const node of nodes) {
635
+ switch (node.type) {
636
+ case "text":
637
+ case "expression":
638
+ continue;
639
+ case "element":
640
+ if (!isFoldableAttrs(node))
641
+ return false;
642
+ if (!isFoldableTree(node.children))
643
+ return false;
644
+ continue;
645
+ default:
646
+ return false;
647
+ }
648
+ }
649
+ return true;
650
+ }
651
+ function isFoldableAttrs(element) {
652
+ for (const attr of element.attrs) {
653
+ if (attr.clientOnly)
654
+ continue;
655
+ switch (attr.value.kind) {
656
+ case "literal":
657
+ case "boolean-attr":
658
+ case "boolean-shorthand":
659
+ continue;
660
+ case "expression":
661
+ if (!attr.value.parsed || !ALLOWED_ATTR_EXPRESSION_KINDS.has(attr.value.parsed.kind))
662
+ return false;
663
+ continue;
664
+ default:
665
+ return false;
666
+ }
667
+ }
668
+ return true;
669
+ }
670
+ function allExpressionsFoldFor(nodes, bindings) {
671
+ for (const node of nodes) {
672
+ if (node.type === "expression") {
673
+ if (node.clientOnly)
674
+ continue;
675
+ if (!node.parsed || !resolvesToScalar(node.parsed, bindings))
676
+ return false;
677
+ continue;
678
+ }
679
+ if (node.type === "element") {
680
+ for (const attr of node.attrs) {
681
+ if (attr.clientOnly)
682
+ continue;
683
+ if (attr.value.kind !== "expression")
684
+ continue;
685
+ if (!attr.value.parsed || !resolvesToScalar(attr.value.parsed, bindings))
686
+ return false;
687
+ }
688
+ if (!allExpressionsFoldFor(node.children, bindings))
689
+ return false;
690
+ }
691
+ }
692
+ return true;
693
+ }
694
+ function resolvesToScalar(expr, bindings) {
695
+ const resolved = evaluateStaticLiteral2(expr, bindings);
696
+ if (resolved === null)
697
+ return false;
698
+ return scalarToGoLiteral(resolved.value) !== null;
699
+ }
700
+
520
701
  // src/adapter/expr/helper-inline.ts
521
702
  function inlineLocalHelperCall(ctx, jsExpr, callParsed) {
522
703
  if (ctx.state.localHelperNames.size === 0)
@@ -704,7 +885,7 @@ function forEachValueChild(n, visit) {
704
885
 
705
886
  // src/adapter/expr/url-builder.ts
706
887
  import {
707
- parseExpression,
888
+ parseExpression as parseExpression2,
708
889
  stringifyParsedExpr,
709
890
  isValidHelperId
710
891
  } from "@barefootjs/jsx";
@@ -737,7 +918,7 @@ function lowerRegisteredCall(ctx, jsExpr, preParsed) {
737
918
  if (!call) {
738
919
  if (!/^\s*[A-Za-z_$][\w$]*\s*\(/.test(jsExpr))
739
920
  return null;
740
- const parsed = parseExpression(jsExpr);
921
+ const parsed = parseExpression2(jsExpr);
741
922
  if (parsed.kind !== "call")
742
923
  return null;
743
924
  call = parsed;
@@ -936,7 +1117,7 @@ function convertInitialValue(ctx, value, typeInfo, propsParams, preParsed) {
936
1117
  return `in.${capitalizeFieldName(value)}`;
937
1118
  }
938
1119
  }
939
- const propName = ctx.extractPropNameFromInitialValue(value);
1120
+ const propName = ctx.extractPropNameFromInitialValue(value, preParsed);
940
1121
  if (propName && propsParams?.some((p) => p.name === propName)) {
941
1122
  return `in.${capitalizeFieldName(propName)}`;
942
1123
  }
@@ -1831,7 +2012,7 @@ function propsAccessNameFromParsed2(ctx, node) {
1831
2012
 
1832
2013
  // src/adapter/spread/spread-codegen.ts
1833
2014
  import ts2 from "typescript";
1834
- import { parseExpression as parseExpression2, parseRecordIndexAccess } from "@barefootjs/jsx";
2015
+ import { parseExpression as parseExpression3, parseRecordIndexAccess } from "@barefootjs/jsx";
1835
2016
  function collectSpreadSlots(ctx, node) {
1836
2017
  const result = [];
1837
2018
  collectSpreadSlotsRecursive(ctx, node, result);
@@ -1938,7 +2119,7 @@ function parsedObjectLiteralToGoMap(parsed) {
1938
2119
  }
1939
2120
  function buildSpreadInitializer(ctx, spreadExpr, ir, parsed) {
1940
2121
  const trimmed = spreadExpr.trim();
1941
- const conditionalTree = parsed ?? parseExpression2(trimmed);
2122
+ const conditionalTree = parsed ?? parseExpression3(trimmed);
1942
2123
  const conditional = buildConditionalSpreadInitializer(ctx, conditionalTree, ir);
1943
2124
  if (conditional !== undefined)
1944
2125
  return conditional;
@@ -1969,7 +2150,7 @@ function buildSpreadInitializer(ctx, spreadExpr, ir, parsed) {
1969
2150
  if (localConst?.value !== undefined) {
1970
2151
  const initTrimmed = localConst.value.trim();
1971
2152
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(initTrimmed)) {
1972
- const resolved = buildConditionalSpreadInitializer(ctx, parseExpression2(initTrimmed), ir);
2153
+ const resolved = buildConditionalSpreadInitializer(ctx, parseExpression3(initTrimmed), ir);
1973
2154
  if (resolved)
1974
2155
  return resolved;
1975
2156
  if (resolved === null)
@@ -2080,11 +2261,12 @@ function recordIndexAccessToGoMap(ctx, val, ir) {
2080
2261
  }
2081
2262
 
2082
2263
  // src/adapter/props/prop-types.ts
2264
+ import { isBooleanAttr } from "@barefootjs/jsx";
2083
2265
  function buildPropTypeOverrides(ctx, ir) {
2084
2266
  const overrides = new Map;
2085
2267
  for (const signal of ir.metadata.signals) {
2086
2268
  const propNames = [signal.initialValue];
2087
- const extracted = ctx.extractPropNameFromInitialValue(signal.initialValue);
2269
+ const extracted = ctx.extractPropNameFromInitialValue(signal.initialValue, signal.parsed);
2088
2270
  if (extracted)
2089
2271
  propNames.push(extracted);
2090
2272
  for (const propName of propNames) {
@@ -2141,11 +2323,96 @@ function collectToFixedPropNames(root) {
2141
2323
  walk(root);
2142
2324
  return names;
2143
2325
  }
2326
+ var NULLISH_SCALAR_GO_TYPES = new Set(["string", "int", "float64", "bool"]);
2327
+ function collectNullishConsumedPropNames(ctx, ir) {
2328
+ const names = new Set;
2329
+ const optionalParams = new Set(ir.metadata.propsParams.filter((p) => p.optional && p.defaultValue == null).map((p) => p.name));
2330
+ if (optionalParams.size === 0)
2331
+ return names;
2332
+ const propsObject = ctx.state.propsObjectName;
2333
+ const propNameOfLeft = (left) => {
2334
+ if (left.kind === "identifier")
2335
+ return left.name;
2336
+ if (left.kind === "member" && !left.computed && left.object.kind === "identifier" && left.object.name === propsObject) {
2337
+ return left.property;
2338
+ }
2339
+ return null;
2340
+ };
2341
+ const isZeroEquivalentLiteral = (right) => right.kind === "literal" && (right.value === "" || right.value === false || right.value === null || right.literalType === "number" && Number(right.value) === 0);
2342
+ const walk = (node) => {
2343
+ if (!node || typeof node !== "object")
2344
+ return;
2345
+ if (Array.isArray(node)) {
2346
+ for (const item of node)
2347
+ walk(item);
2348
+ return;
2349
+ }
2350
+ const rec = node;
2351
+ if (rec.kind === "logical" && rec.op === "??" && rec.left && rec.right) {
2352
+ const propName = propNameOfLeft(rec.left);
2353
+ if (propName && optionalParams.has(propName) && !isZeroEquivalentLiteral(rec.right)) {
2354
+ names.add(propName);
2355
+ }
2356
+ }
2357
+ for (const value of Object.values(rec))
2358
+ walk(value);
2359
+ };
2360
+ walk(ir.root);
2361
+ for (const signal of ir.metadata.signals) {
2362
+ const match = ctx.extractPropFallback(signal.initialValue, signal.parsed);
2363
+ if (!match || !optionalParams.has(match.propName))
2364
+ continue;
2365
+ const f = match.goFallback;
2366
+ if (f === '""' || f === "false" || f === "nil" || Number(f) === 0)
2367
+ continue;
2368
+ names.add(match.propName);
2369
+ }
2370
+ return names;
2371
+ }
2372
+ function collectOmittableAttrConsumedPropNames(ctx, ir) {
2373
+ const names = new Set;
2374
+ const optionalParams = new Set(ir.metadata.propsParams.filter((p) => p.optional && p.defaultValue == null).map((p) => p.name));
2375
+ if (optionalParams.size === 0)
2376
+ return names;
2377
+ const propsObject = ctx.state.propsObjectName;
2378
+ const walk = (node) => {
2379
+ if (!node || typeof node !== "object")
2380
+ return;
2381
+ if (Array.isArray(node)) {
2382
+ for (const item of node)
2383
+ walk(item);
2384
+ return;
2385
+ }
2386
+ const rec = node;
2387
+ if (rec.type === "element" && Array.isArray(rec.attrs)) {
2388
+ for (const attr of rec.attrs) {
2389
+ if (attr.name === "class" || attr.name === "className" || attr.name === "style")
2390
+ continue;
2391
+ if (isBooleanAttr(attr.name))
2392
+ continue;
2393
+ if (attr.value?.kind !== "expression" || attr.value.presenceOrUndefined)
2394
+ continue;
2395
+ const bareId = String(attr.value.expr ?? "").trim();
2396
+ const propName = propsObject && bareId.startsWith(`${propsObject}.`) ? bareId.slice(propsObject.length + 1) : bareId;
2397
+ if (/^[A-Za-z_$][\w$]*$/.test(propName) && optionalParams.has(propName)) {
2398
+ names.add(propName);
2399
+ }
2400
+ }
2401
+ }
2402
+ for (const value of Object.values(rec))
2403
+ walk(value);
2404
+ };
2405
+ walk(ir.root);
2406
+ return names;
2407
+ }
2144
2408
  function resolvePropGoType(ctx, param, propTypeOverrides) {
2145
2409
  const base = propTypeOverrides.get(param.name) ?? typeInfoToGo(ctx, param.type, param.defaultValue);
2146
2410
  if (param.optional && ctx.state.localStructFields.has(base)) {
2147
2411
  return "map[string]interface{}";
2148
2412
  }
2413
+ if (param.optional && param.type.kind === "primitive" && (ctx.state.nullishConsumedPropNames.has(param.name) || ctx.state.omittableAttrConsumedPropNames.has(param.name)) && NULLISH_SCALAR_GO_TYPES.has(base)) {
2414
+ return "interface{}";
2415
+ }
2149
2416
  return base;
2150
2417
  }
2151
2418
  function collectNillablePropNames(ctx, ir) {
@@ -2160,6 +2427,7 @@ function collectNillablePropNames(ctx, ir) {
2160
2427
  }
2161
2428
 
2162
2429
  // src/adapter/props/prop-classes.ts
2430
+ import { collectLoopBoundNames } from "@barefootjs/jsx";
2163
2431
  function isStringTypeInfo(type) {
2164
2432
  return type.kind === "primitive" && type.primitive === "string";
2165
2433
  }
@@ -2180,6 +2448,13 @@ function collectStringValueNames(ir) {
2180
2448
  if (isStringTypeInfo(p.type))
2181
2449
  names.add(p.name);
2182
2450
  }
2451
+ for (const c of ir.metadata.localConstants) {
2452
+ if (c.type !== null && isStringTypeInfo(c.type) || isBareStringLiteral(c.value)) {
2453
+ names.add(c.name);
2454
+ }
2455
+ }
2456
+ for (const bound of collectLoopBoundNames(ir))
2457
+ names.delete(bound);
2183
2458
  return names;
2184
2459
  }
2185
2460
 
@@ -2216,14 +2491,15 @@ class GoTemplateAdapter extends BaseAdapter {
2216
2491
  state: this.state,
2217
2492
  convertExpressionToGo: (jsExpr, out, preParsed) => this.convertExpressionToGo(jsExpr, out, preParsed),
2218
2493
  convertConditionToGo: (jsCondition, preParsed) => this.convertConditionToGo(jsCondition, preParsed),
2219
- extractPropNameFromInitialValue: (initialValue) => this.extractPropNameFromInitialValue(initialValue),
2220
- extractPropFallback: (initialValue) => this.extractPropFallback(initialValue),
2494
+ extractPropNameFromInitialValue: (initialValue, preParsed) => this.extractPropNameFromInitialValue(initialValue, preParsed),
2495
+ extractPropFallback: (initialValue, preParsed) => this.extractPropFallback(initialValue, preParsed),
2221
2496
  resolveModuleStringConst: (name) => this.resolveModuleStringConst(name)
2222
2497
  };
2223
2498
  get errors() {
2224
2499
  return this.state.errors;
2225
2500
  }
2226
2501
  inLoop = false;
2502
+ bakedStaticChildLoopCache = new Map;
2227
2503
  loopParamStack = [];
2228
2504
  loopKeyDepthStack = [];
2229
2505
  loopScalarItemStack = [];
@@ -2231,6 +2507,8 @@ class GoTemplateAdapter extends BaseAdapter {
2231
2507
  loopVarRefCount = new Map;
2232
2508
  loopBindingStack = [];
2233
2509
  loopRestExcludeStack = [];
2510
+ staticLoopItemStack = [];
2511
+ staticLoopBakeFailed = false;
2234
2512
  childComponentShapes = new Map;
2235
2513
  childContextConsumers = new Map;
2236
2514
  constructor(options = {}) {
@@ -2246,6 +2524,8 @@ class GoTemplateAdapter extends BaseAdapter {
2246
2524
  this.state.restPropsName = ir.metadata.restPropsName ?? null;
2247
2525
  this.state.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
2248
2526
  this.state.localConstants = ir.metadata.localConstants ?? [];
2527
+ this.state.staticLoopSourceBoundNames = collectLoopBoundNames2(ir);
2528
+ this.bakedStaticChildLoopCache = new Map;
2249
2529
  this.state.localHelperNames = new Set(this.state.localConstants.filter((c) => !c.isModule && c.containsArrow).map((c) => c.name));
2250
2530
  this.state.currentMemos = ir.metadata.memos ?? [];
2251
2531
  this.state.currentTypeDefinitions = ir.metadata.typeDefinitions ?? [];
@@ -2264,6 +2544,10 @@ class GoTemplateAdapter extends BaseAdapter {
2264
2544
  }
2265
2545
  this.state.loweringMatchers = prepareLoweringMatchers(ir.metadata);
2266
2546
  augmentInheritedPropAccesses(ir);
2547
+ this.buildLocalTypeTables(ir, ir.metadata.componentName);
2548
+ this.state.nullishConsumedPropNames = collectNullishConsumedPropNames(this.emitCtx, ir);
2549
+ this.state.omittableAttrConsumedPropNames = collectOmittableAttrConsumedPropNames(this.emitCtx, ir);
2550
+ this.state.nillablePropNames = collectNillablePropNames(this.emitCtx, ir);
2267
2551
  }
2268
2552
  generate(ir, options) {
2269
2553
  this.state.componentName = ir.metadata.componentName;
@@ -2272,7 +2556,6 @@ class GoTemplateAdapter extends BaseAdapter {
2272
2556
  this.state.templateVarCounter = 0;
2273
2557
  this.state.pendingChildrenDefines = [];
2274
2558
  this.primeCompileState(ir);
2275
- this.state.nillablePropNames = collectNillablePropNames(this.emitCtx, ir);
2276
2559
  this.state.stringValueNames = collectStringValueNames(ir);
2277
2560
  if (!options?.siblingTemplatesRegistered) {
2278
2561
  this.checkImportedLoopChildComponents(ir);
@@ -2627,6 +2910,8 @@ ${goFields.join(`
2627
2910
  lines.push(` ${fieldName} ${goType}`);
2628
2911
  }
2629
2912
  for (const nested of inputNested) {
2913
+ if (nested.loopMarkerId && this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey))
2914
+ continue;
2630
2915
  lines.push(` ${nested.name}s []${nested.name}Input`);
2631
2916
  }
2632
2917
  const takenInput = new Set(ir.metadata.propsParams.map((p) => capitalizeFieldName(p.name)));
@@ -2758,6 +3043,21 @@ ${goFields.join(`
2758
3043
  const staticWithoutBody = staticNested.filter((n) => !n.bodyChildren || n.bodyChildren.length === 0);
2759
3044
  for (const nested of staticWithoutBody) {
2760
3045
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
3046
+ const baked = nested.loopMarkerId ? this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey) : null;
3047
+ if (baked) {
3048
+ lines.push(` ${varName} := make([]${nested.name}Props, ${baked.items.length})`);
3049
+ baked.items.forEach((item, i) => {
3050
+ const fields = item.inputFields.map((f) => `${f.goField}: ${f.goValue}`).join(", ");
3051
+ lines.push(` ${varName}[${i}] = New${nested.name}Props(${nested.name}Input{${fields}})`);
3052
+ lines.push(` ${varName}[${i}].BfParent = scopeID`);
3053
+ lines.push(` ${varName}[${i}].BfMount = "${nested.slotId}"`);
3054
+ if (item.dataKey !== null) {
3055
+ lines.push(` ${varName}[${i}].BfDataKey = ${JSON.stringify(item.dataKey)}`);
3056
+ }
3057
+ });
3058
+ lines.push("");
3059
+ continue;
3060
+ }
2761
3061
  lines.push(` ${varName} := make([]${nested.name}Props, len(in.${nested.name}s))`);
2762
3062
  lines.push(` for i, item := range in.${nested.name}s {`);
2763
3063
  lines.push(` ${varName}[i] = New${nested.name}Props(item)`);
@@ -2774,10 +3074,18 @@ ${goFields.join(`
2774
3074
  this.emitStaticBodyWrappers(lines, ir, componentName, staticWithBody, emittedWrapperVars);
2775
3075
  const propFallbackVars = this.collectPropFallbackVars(ir);
2776
3076
  for (const [, info] of propFallbackVars) {
2777
- lines.push(` ${info.varName} := in.${info.fieldName}`);
2778
- lines.push(` if ${info.varName} == ${info.zeroLiteral} {`);
2779
- lines.push(` ${info.varName} = ${info.goFallback}`);
2780
- lines.push(` }`);
3077
+ if (info.assertType) {
3078
+ const deref = info.assertType === "int" ? `bf.ToInt(in.${info.fieldName})` : info.assertType === "float64" ? `bf.ToFloat64(in.${info.fieldName})` : `in.${info.fieldName}.(${info.assertType})`;
3079
+ lines.push(` var ${info.varName} ${info.assertType} = ${info.goFallback}`);
3080
+ lines.push(` if in.${info.fieldName} != nil {`);
3081
+ lines.push(` ${info.varName} = ${deref}`);
3082
+ lines.push(` }`);
3083
+ } else {
3084
+ lines.push(` ${info.varName} := in.${info.fieldName}`);
3085
+ lines.push(` if ${info.varName} == ${info.zeroLiteral} {`);
3086
+ lines.push(` ${info.varName} = ${info.goFallback}`);
3087
+ lines.push(` }`);
3088
+ }
2781
3089
  }
2782
3090
  if (propFallbackVars.size > 0)
2783
3091
  lines.push("");
@@ -2857,7 +3165,7 @@ ${goFields.join(`
2857
3165
  const fieldName = capitalizeFieldName(signal.getter);
2858
3166
  if (propFieldNames.has(fieldName))
2859
3167
  continue;
2860
- const fallbackMatch = this.extractPropFallback(signal.initialValue);
3168
+ const fallbackMatch = this.extractPropFallback(signal.initialValue, signal.parsed);
2861
3169
  const hoisted = fallbackMatch ? propFallbackVars.get(fallbackMatch.propName) : undefined;
2862
3170
  if (hoisted) {
2863
3171
  lines.push(` ${fieldName}: ${hoisted.varName},`);
@@ -3703,8 +4011,9 @@ ${goFields.join(`
3703
4011
  for (const nested of findNestedComponents(ir.root)) {
3704
4012
  localTaken.add(`${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`);
3705
4013
  }
4014
+ const propTypeOverrides = buildPropTypeOverrides(this.emitCtx, ir);
3706
4015
  for (const signal of ir.metadata.signals) {
3707
- const match = this.extractPropFallback(signal.initialValue);
4016
+ const match = this.extractPropFallback(signal.initialValue, signal.parsed);
3708
4017
  if (!match)
3709
4018
  continue;
3710
4019
  if (result.has(match.propName))
@@ -3715,6 +4024,8 @@ ${goFields.join(`
3715
4024
  if (goPropDefault(param.defaultValue) !== null)
3716
4025
  continue;
3717
4026
  const fieldName = capitalizeFieldName(match.propName);
4027
+ const concreteType = propTypeOverrides.get(param.name) ?? typeInfoToGo(this.emitCtx, param.type, param.defaultValue);
4028
+ const nullishLowered = NULLISH_SCALAR_GO_TYPES.has(concreteType) && resolvePropGoType(this.emitCtx, param, propTypeOverrides) === "interface{}";
3718
4029
  let zeroLiteral;
3719
4030
  if (match.goFallback === "true" || match.goFallback === "false") {
3720
4031
  zeroLiteral = "false";
@@ -3725,20 +4036,31 @@ ${goFields.join(`
3725
4036
  } else {
3726
4037
  continue;
3727
4038
  }
3728
- if (match.goFallback === zeroLiteral)
3729
- continue;
3730
- if (zeroLiteral === "0" && Number(match.goFallback) === 0)
3731
- continue;
4039
+ if (!nullishLowered) {
4040
+ if (match.goFallback === zeroLiteral)
4041
+ continue;
4042
+ if (zeroLiteral === "0" && Number(match.goFallback) === 0)
4043
+ continue;
4044
+ }
3732
4045
  let varName = match.propName;
3733
4046
  while (localTaken.has(varName) || GO_KEYWORDS.has(varName)) {
3734
4047
  varName += "_";
3735
4048
  }
3736
4049
  localTaken.add(varName);
3737
- result.set(match.propName, { varName, fieldName, goFallback: match.goFallback, zeroLiteral });
4050
+ result.set(match.propName, {
4051
+ varName,
4052
+ fieldName,
4053
+ goFallback: match.goFallback,
4054
+ zeroLiteral,
4055
+ ...nullishLowered ? { assertType: concreteType } : {}
4056
+ });
3738
4057
  }
3739
4058
  return result;
3740
4059
  }
3741
- extractPropFallback(initialValue) {
4060
+ extractPropFallback(initialValue, preParsed) {
4061
+ const structural = preParsed ? this.extractPropFallbackFromParsed(preParsed) : null;
4062
+ if (structural)
4063
+ return structural;
3742
4064
  if (!this.state.propsObjectName)
3743
4065
  return null;
3744
4066
  const trimmed = initialValue.trim();
@@ -3752,9 +4074,38 @@ ${goFields.join(`
3752
4074
  return null;
3753
4075
  return { propName: m[1], goFallback };
3754
4076
  }
3755
- extractPropNameFromInitialValue(initialValue) {
3756
- if (!this.state.propsObjectName)
4077
+ extractPropFallbackFromParsed(preParsed) {
4078
+ if (preParsed.kind !== "logical" || preParsed.op !== "??")
4079
+ return null;
4080
+ const left = preParsed.left;
4081
+ const propName = left.kind === "identifier" && !this.state.propsObjectName ? left.name : left.kind === "member" && !left.computed && left.object.kind === "identifier" && left.object.name === this.state.propsObjectName ? left.property : null;
4082
+ if (!propName)
4083
+ return null;
4084
+ let right = preParsed.right;
4085
+ let negate = "";
4086
+ if (right.kind === "unary" && right.op === "-") {
4087
+ negate = "-";
4088
+ right = right.argument;
4089
+ }
4090
+ if (right.kind !== "literal")
4091
+ return null;
4092
+ if (negate && right.literalType !== "number")
4093
+ return null;
4094
+ if (right.literalType === "string") {
4095
+ return { propName, goFallback: JSON.stringify(right.value) };
4096
+ }
4097
+ const goFallback = goPropDefault(negate + (right.raw ?? String(right.value)));
4098
+ if (goFallback === null)
4099
+ return null;
4100
+ return { propName, goFallback };
4101
+ }
4102
+ extractPropNameFromInitialValue(initialValue, preParsed) {
4103
+ if (!this.state.propsObjectName) {
4104
+ if (preParsed?.kind === "logical" && (preParsed.op === "??" || preParsed.op === "||") && preParsed.left.kind === "identifier") {
4105
+ return preParsed.left.name;
4106
+ }
3757
4107
  return null;
4108
+ }
3758
4109
  const trimmed = initialValue.trim();
3759
4110
  const name = this.state.propsObjectName;
3760
4111
  const direct = new RegExp(`^${name}\\.(\\w+)(?:\\s*(?:\\?\\?|\\|\\|)\\s*.+)?$`);
@@ -3811,7 +4162,8 @@ ${goFields.join(`
3811
4162
  renderElement(element) {
3812
4163
  const tag = element.tag;
3813
4164
  const attrs = this.renderAttributes(element);
3814
- const children = this.renderChildren(element.children);
4165
+ const dangerousHtml = this.renderDangerousInnerHtml(element);
4166
+ const children = dangerousHtml !== null ? dangerousHtml : this.renderChildren(element.children);
3815
4167
  let hydrationAttrs = "";
3816
4168
  if (element.needsScope) {
3817
4169
  hydrationAttrs += ` ${this.renderScopeMarker(".ScopeID")}`;
@@ -3846,6 +4198,22 @@ ${goFields.join(`
3846
4198
  }
3847
4199
  return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
3848
4200
  }
4201
+ renderDangerousInnerHtml(element) {
4202
+ const resolution = resolveDangerousInnerHtml(element);
4203
+ if (!resolution)
4204
+ return null;
4205
+ if (resolution.kind === "dynamic") {
4206
+ this.errors.push(dangerousInnerHtmlDiagnostic(resolution.expr, resolution.loc));
4207
+ return "";
4208
+ }
4209
+ const violation = dangerousInnerHtmlMetacharViolation(resolution.html, this.name);
4210
+ if (violation) {
4211
+ const attr = element.attrs.find(isDangerousInnerHtmlAttr);
4212
+ this.errors.push(dangerousInnerHtmlDiagnostic(`{ __html: ${JSON.stringify(resolution.html)} }`, attr.loc, violation));
4213
+ return "";
4214
+ }
4215
+ return resolution.html;
4216
+ }
3849
4217
  renderExpression(expr) {
3850
4218
  if (expr.clientOnly) {
3851
4219
  if (expr.slotId) {
@@ -4149,8 +4517,20 @@ ${goFields.join(`
4149
4517
  const wrapRight = wrapIfMultiToken(emit(right));
4150
4518
  if (op === "&&")
4151
4519
  return `and ${wrapLeft} ${wrapRight}`;
4520
+ if (op === "??" && this.nillablePropNameOf(left) !== null) {
4521
+ return `bf_nullish ${wrapLeft} ${wrapRight}`;
4522
+ }
4152
4523
  return `or ${wrapLeft} ${wrapRight}`;
4153
4524
  }
4525
+ nillablePropNameOf(expr) {
4526
+ let name = null;
4527
+ if (expr.kind === "identifier") {
4528
+ name = expr.name;
4529
+ } else if (expr.kind === "member" && !expr.computed && expr.object.kind === "identifier" && expr.object.name === this.state.propsObjectName) {
4530
+ name = expr.property;
4531
+ }
4532
+ return name !== null && this.state.nullishConsumedPropNames.has(name) && this.state.nillablePropNames.has(name) ? name : null;
4533
+ }
4154
4534
  conditional(test, consequent, alternate, emit) {
4155
4535
  const t = emit(test);
4156
4536
  const c = this.renderConditionalBranch(consequent);
@@ -4579,8 +4959,8 @@ ${goFields.join(`
4579
4959
  const value = negated ? "false" : "true";
4580
4960
  return `len (bf_filter ${arrayExpr} "${field}" ${value})`;
4581
4961
  }
4582
- renderPredicateCondition(pred, param) {
4583
- return this.renderFilterExpr(pred, param);
4962
+ renderPredicateCondition(pred, param, datumField) {
4963
+ return this.renderFilterExpr(pred, param, new Map, datumField ?? undefined);
4584
4964
  }
4585
4965
  needsParens(expr) {
4586
4966
  return expr.kind === "logical" || expr.kind === "unary" || expr.kind === "conditional";
@@ -4601,21 +4981,23 @@ ${goFields.join(`
4601
4981
  }
4602
4982
  return null;
4603
4983
  }
4604
- renderFilterExpr(expr, param, localVarMap = new Map) {
4984
+ renderFilterExpr(expr, param, localVarMap = new Map, datumField) {
4605
4985
  if (this.filterExprDepth === 0)
4606
4986
  this.filterExprUnsupported = false;
4607
4987
  this.filterExprDepth++;
4608
4988
  try {
4609
- return this.renderFilterExprNode(expr, param, localVarMap);
4989
+ return this.renderFilterExprNode(expr, param, localVarMap, datumField);
4610
4990
  } finally {
4611
4991
  this.filterExprDepth--;
4612
4992
  }
4613
4993
  }
4614
- renderFilterExprNode(expr, param, localVarMap) {
4994
+ renderFilterExprNode(expr, param, localVarMap, datumField) {
4995
+ const paramPrefix = datumField ? `.${datumField}` : "";
4996
+ const paramDot = paramPrefix || ".";
4615
4997
  switch (expr.kind) {
4616
4998
  case "identifier": {
4617
4999
  if (expr.name === param) {
4618
- return ".";
5000
+ return paramDot;
4619
5001
  }
4620
5002
  const signal = localVarMap.get(expr.name);
4621
5003
  if (signal) {
@@ -4633,24 +5015,24 @@ ${goFields.join(`
4633
5015
  return String(expr.value);
4634
5016
  case "member": {
4635
5017
  if (expr.object.kind === "identifier" && expr.object.name === param) {
4636
- return `.${capitalizeFieldName(expr.property)}`;
5018
+ return `${paramPrefix}.${capitalizeFieldName(expr.property)}`;
4637
5019
  }
4638
5020
  if (expr.property === "length") {
4639
5021
  const innerHO = this.higherOrderShapeOf(expr.object);
4640
5022
  if (innerHO && innerHO.method === "filter") {
4641
- const lenExpr = this.renderFilterLengthExpr(innerHO, (e) => this.renderFilterExpr(e, param, localVarMap));
5023
+ const lenExpr = this.renderFilterLengthExpr(innerHO, (e) => this.renderFilterExpr(e, param, localVarMap, datumField));
4642
5024
  if (lenExpr)
4643
5025
  return `(${lenExpr})`;
4644
5026
  }
4645
5027
  }
4646
- const obj = this.renderFilterExpr(expr.object, param, localVarMap);
5028
+ const obj = this.renderFilterExpr(expr.object, param, localVarMap, datumField);
4647
5029
  if (this.filterExprUnsupported)
4648
5030
  return "false";
4649
5031
  return `${obj}.${capitalizeFieldName(expr.property)}`;
4650
5032
  }
4651
5033
  case "call": {
4652
5034
  if (expr.callee.kind === "member" && expr.callee.object.kind === "identifier" && expr.callee.object.name === param) {
4653
- return `.${capitalizeFieldName(expr.callee.property)}`;
5035
+ return `${paramPrefix}.${capitalizeFieldName(expr.callee.property)}`;
4654
5036
  }
4655
5037
  if (expr.callee.kind === "identifier" && expr.args.length === 0) {
4656
5038
  return `$.${capitalizeFieldName(expr.callee.name)}`;
@@ -4658,13 +5040,13 @@ ${goFields.join(`
4658
5040
  if (asCallbackMethodCall3(expr) !== null) {
4659
5041
  return this.refuseFilterExprNode(expr);
4660
5042
  }
4661
- const result = this.renderFilterExpr(expr.callee, param, localVarMap);
5043
+ const result = this.renderFilterExpr(expr.callee, param, localVarMap, datumField);
4662
5044
  if (this.filterExprUnsupported)
4663
5045
  return "false";
4664
5046
  return result;
4665
5047
  }
4666
5048
  case "unary": {
4667
- const arg = this.renderFilterExpr(expr.argument, param, localVarMap);
5049
+ const arg = this.renderFilterExpr(expr.argument, param, localVarMap, datumField);
4668
5050
  if (this.filterExprUnsupported)
4669
5051
  return "false";
4670
5052
  if (expr.op === "!") {
@@ -4677,10 +5059,10 @@ ${goFields.join(`
4677
5059
  return arg;
4678
5060
  }
4679
5061
  case "binary": {
4680
- const left = this.renderFilterExpr(expr.left, param, localVarMap);
5062
+ const left = this.renderFilterExpr(expr.left, param, localVarMap, datumField);
4681
5063
  if (this.filterExprUnsupported)
4682
5064
  return "false";
4683
- const right = this.renderFilterExpr(expr.right, param, localVarMap);
5065
+ const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField);
4684
5066
  if (this.filterExprUnsupported)
4685
5067
  return "false";
4686
5068
  switch (expr.op) {
@@ -4711,10 +5093,10 @@ ${goFields.join(`
4711
5093
  }
4712
5094
  }
4713
5095
  case "logical": {
4714
- const left = this.renderFilterExpr(expr.left, param, localVarMap);
5096
+ const left = this.renderFilterExpr(expr.left, param, localVarMap, datumField);
4715
5097
  if (this.filterExprUnsupported)
4716
5098
  return "false";
4717
- const right = this.renderFilterExpr(expr.right, param, localVarMap);
5099
+ const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField);
4718
5100
  if (this.filterExprUnsupported)
4719
5101
  return "false";
4720
5102
  if (expr.op === "&&") {
@@ -4782,11 +5164,22 @@ ${goFields.join(`
4782
5164
  if (trimmed === "null" || trimmed === "undefined") {
4783
5165
  return '""';
4784
5166
  }
5167
+ if (this.staticLoopItemStack.length > 0) {
5168
+ const top = this.staticLoopItemStack[this.staticLoopItemStack.length - 1];
5169
+ const parsedForBake = preParsed ?? parseExpression4(trimmed);
5170
+ const resolved = evaluateStaticLiteral3(parsedForBake, new Map([[top.param, top.item]]));
5171
+ const literal = resolved !== null ? scalarToGoLiteral(resolved.value) : null;
5172
+ if (literal !== null) {
5173
+ return literal;
5174
+ }
5175
+ this.staticLoopBakeFailed = true;
5176
+ return '""';
5177
+ }
4785
5178
  const staticIndexed = this.resolveStaticRecordLiteralIndex(trimmed);
4786
5179
  if (staticIndexed !== null) {
4787
5180
  return staticIndexed;
4788
5181
  }
4789
- if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
5182
+ if (!this.isLoopShadowedName(trimmed) && /^[A-Za-z_$][\w$]*$/.test(trimmed)) {
4790
5183
  const litConst = (this.state.localConstants ?? []).find((c) => c.name === trimmed);
4791
5184
  if (litConst?.value !== undefined) {
4792
5185
  const v = litConst.value.trim();
@@ -4804,7 +5197,7 @@ ${goFields.join(`
4804
5197
  if (inlined !== null) {
4805
5198
  return this.convertExpressionToGo(stringifyParsedExpr2(inlined), out, inlined);
4806
5199
  }
4807
- const parsed = preParsed ?? parseExpression3(trimmed);
5200
+ const parsed = preParsed ?? parseExpression4(trimmed);
4808
5201
  const support = isSupported(parsed);
4809
5202
  if (!support.supported) {
4810
5203
  this.state.errors.push({
@@ -4822,10 +5215,15 @@ ${goFields.join(`
4822
5215
  out.parsed = parsed;
4823
5216
  return this.renderParsedExpr(parsed);
4824
5217
  }
5218
+ isLoopShadowedName(name) {
5219
+ return this.loopParamStack.length > 0 && this.loopParamStack[this.loopParamStack.length - 1] === name || this.loopVarRefCount.has(name) || this.isOuterLoopParam(name) || this.loopBindingStack.some((bindings) => bindings.has(name));
5220
+ }
4825
5221
  resolveStaticRecordLiteralIndex(jsExpr) {
4826
5222
  const m = /^([A-Za-z_$][\w$]*)\[\s*(?:'([^']*)'|"([^"]*)")\s*\]$/.exec(jsExpr) ?? /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/.exec(jsExpr);
4827
5223
  if (!m)
4828
5224
  return null;
5225
+ if (this.isLoopShadowedName(m[1]))
5226
+ return null;
4829
5227
  const key = m[2] ?? m[3];
4830
5228
  const constInfo = (this.state.localConstants ?? []).find((c) => c.name === m[1] && c.isModule);
4831
5229
  if (constInfo?.value === undefined)
@@ -4931,7 +5329,7 @@ ${goFields.join(`
4931
5329
  }
4932
5330
  convertConditionToGo(jsCondition, preParsed) {
4933
5331
  const trimmed = jsCondition.trim();
4934
- const parsed = preParsed ?? parseExpression3(trimmed);
5332
+ const parsed = preParsed ?? parseExpression4(trimmed);
4935
5333
  const support = isSupported(parsed);
4936
5334
  if (!support.supported) {
4937
5335
  this.state.errors.push({
@@ -5152,6 +5550,29 @@ ${goFields.join(`
5152
5550
  }
5153
5551
  return;
5154
5552
  }
5553
+ wrapperDatumField(loop) {
5554
+ if (!loop.childComponent)
5555
+ return null;
5556
+ for (const prop of loop.childComponent.props) {
5557
+ if (prop.isEventHandler)
5558
+ continue;
5559
+ if (prop.value.kind !== "expression")
5560
+ continue;
5561
+ const parsed = prop.value.parsed;
5562
+ const isBareParamRef = parsed ? parsed.kind === "identifier" && parsed.name === loop.param : prop.value.expr.trim() === loop.param;
5563
+ if (isBareParamRef)
5564
+ return capitalizeFieldName(prop.name);
5565
+ }
5566
+ return null;
5567
+ }
5568
+ getBakedStaticChildLoop(markerId, childComponent, arrayParsed, param, key) {
5569
+ if (this.bakedStaticChildLoopCache.has(markerId)) {
5570
+ return this.bakedStaticChildLoopCache.get(markerId) ?? null;
5571
+ }
5572
+ const result = analyzeBakeableStaticChildLoop({ props: childComponent.props, loopArrayParsed: arrayParsed, loopParam: param, loopKey: key }, this.state.localConstants, { isNameShadowed: (name) => this.state.staticLoopSourceBoundNames.has(name) });
5573
+ this.bakedStaticChildLoopCache.set(markerId, result);
5574
+ return result;
5575
+ }
5155
5576
  renderLoop(loop) {
5156
5577
  if (loop.clientOnly) {
5157
5578
  return `{{bfComment "loop:${loop.markerId}"}}{{bfComment "/loop:${loop.markerId}"}}`;
@@ -5172,8 +5593,13 @@ ${goFields.join(`
5172
5593
  }
5173
5594
  });
5174
5595
  }
5596
+ const bakedChildLoop = loop.childComponent ? this.getBakedStaticChildLoop(loop.markerId, loop.childComponent, loop.arrayParsed, loop.param, loop.key ?? undefined) : null;
5597
+ const bakedElementLoop = loop.childComponent ? null : analyzeBakeableStaticElementLoop(loop, this.state.localConstants, { isNameShadowed: (name) => this.state.staticLoopSourceBoundNames.has(name) });
5598
+ if (bakedElementLoop) {
5599
+ return this.renderUnrolledStaticElementLoop(loop, bakedElementLoop.items);
5600
+ }
5175
5601
  const arrayName = loop.array.trim();
5176
- if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
5602
+ if (bakedChildLoop === null && /^[A-Za-z_$][\w$]*$/.test(arrayName)) {
5177
5603
  const arrayConst = this.state.localConstants.find((c) => c.name === arrayName);
5178
5604
  if (arrayConst && !arrayConst.isModule && arrayConst.parsed && !this.isStringExpr(arrayConst.parsed, new Set)) {
5179
5605
  this.state.errors.push({
@@ -5187,7 +5613,7 @@ ${goFields.join(`
5187
5613
  });
5188
5614
  }
5189
5615
  }
5190
- let goArray = this.convertExpressionToGo(loop.array);
5616
+ let goArray = loop.childComponent ? "" : this.convertExpressionToGo(loop.array);
5191
5617
  const param = loop.param;
5192
5618
  let index = loop.index || "_";
5193
5619
  let rangeIndex = index;
@@ -5255,7 +5681,8 @@ ${goFields.join(`
5255
5681
  if (loop.filterPredicate) {
5256
5682
  let filterCond;
5257
5683
  if (loop.filterPredicate.predicate) {
5258
- filterCond = this.renderPredicateCondition(loop.filterPredicate.predicate, loop.filterPredicate.param);
5684
+ const datumField = this.wrapperDatumField(loop);
5685
+ filterCond = this.renderPredicateCondition(loop.filterPredicate.predicate, loop.filterPredicate.param, datumField);
5259
5686
  } else {
5260
5687
  filterCond = "true";
5261
5688
  }
@@ -5263,6 +5690,38 @@ ${goFields.join(`
5263
5690
  }
5264
5691
  return `{{bfComment "loop:${loop.markerId}"}}{{range $${rangeIndex}, $${rangeValue} := ${goArray}}}${itemMarker}${children}{{end}}{{bfComment "/loop:${loop.markerId}"}}`;
5265
5692
  }
5693
+ renderUnrolledStaticElementLoop(loop, items) {
5694
+ this.inLoop = true;
5695
+ this.loopWrapperStack.push(false);
5696
+ this.loopKeyDepthStack.push(loop.depth);
5697
+ this.loopScalarItemStack.push(this.scalarLiteralLoopGoType(loop.arrayParsed, loop.itemType) !== null);
5698
+ this.loopParamStack.push(loop.param);
5699
+ let body = "";
5700
+ for (const item of items) {
5701
+ this.staticLoopItemStack.push({ param: loop.param, item });
5702
+ body += this.renderChildren(loop.children);
5703
+ this.staticLoopItemStack.pop();
5704
+ if (this.staticLoopBakeFailed) {
5705
+ this.staticLoopBakeFailed = false;
5706
+ this.state.errors.push({
5707
+ code: "BF101",
5708
+ severity: "error",
5709
+ message: `Loop array \`${loop.array.trim()}\` could not be fully unrolled — an expression in the loop body did not resolve against every item as the compile-time analysis expected.`,
5710
+ loc: loop.loc ?? this.makeLoc(),
5711
+ suggestion: {
5712
+ message: "This indicates a bug in the Go adapter's static-loop unrolling (#2224) rather than an unsupported source pattern; please file a bug with a reproduction."
5713
+ }
5714
+ });
5715
+ break;
5716
+ }
5717
+ }
5718
+ this.loopParamStack.pop();
5719
+ this.loopScalarItemStack.pop();
5720
+ this.loopKeyDepthStack.pop();
5721
+ this.loopWrapperStack.pop();
5722
+ this.inLoop = false;
5723
+ return `{{bfComment "loop:${loop.markerId}"}}${body}{{bfComment "/loop:${loop.markerId}"}}`;
5724
+ }
5266
5725
  loopItemMarker(loop) {
5267
5726
  if (loop.bodyIsMultiRoot)
5268
5727
  return `{{bfComment "bf-loop-i"}}`;
@@ -5377,12 +5836,12 @@ ${children}`;
5377
5836
  if (css !== null)
5378
5837
  return `style="${css}"`;
5379
5838
  }
5380
- if (isBooleanAttr(name) || value.presenceOrUndefined) {
5839
+ if (isBooleanAttr2(name) || value.presenceOrUndefined) {
5381
5840
  const { condition: goCond, preamble } = this.convertConditionToGo(value.expr, value.parsed);
5382
5841
  const body = name.startsWith("aria-") ? `${name}="true"` : name;
5383
5842
  return `${preamble}{{if ${goCond}}}${body}{{end}}`;
5384
5843
  }
5385
- const parsed = value.parsed ?? parseExpression3(value.expr.trim());
5844
+ const parsed = value.parsed ?? parseExpression4(value.expr.trim());
5386
5845
  if (parsed.kind === "conditional") {
5387
5846
  const undef = (e) => e.kind === "identifier" && (e.name === "undefined" || e.name === "null") || e.kind === "literal" && (e.value === null || e.value === undefined);
5388
5847
  const test = parsed.test;
@@ -5447,7 +5906,7 @@ ${children}`;
5447
5906
  if (!entries)
5448
5907
  return null;
5449
5908
  for (const e of entries) {
5450
- if (e.kind === "expr" && !isSupported(parseExpression3(e.expr)).supported)
5909
+ if (e.kind === "expr" && !isSupported(parseExpression4(e.expr)).supported)
5451
5910
  return null;
5452
5911
  }
5453
5912
  return entries.map((e) => e.kind === "literal" ? `${this.escapeAttrText(e.cssKey)}:${this.escapeAttrText(e.value)}` : `${this.escapeAttrText(e.cssKey)}:{{${this.convertExpressionToGo(e.expr)}}}`).join(";");
@@ -5457,6 +5916,8 @@ ${children}`;
5457
5916
  for (const attr of element.attrs) {
5458
5917
  if (attr.clientOnly)
5459
5918
  continue;
5919
+ if (isDangerousInnerHtmlAttr(attr))
5920
+ continue;
5460
5921
  let attrName;
5461
5922
  if (attr.name === "className")
5462
5923
  attrName = "class";