@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
package/dist/build.js CHANGED
@@ -364,8 +364,8 @@ var init_path = __esm(() => {
364
364
  // src/adapter/go-template-adapter.ts
365
365
  import {
366
366
  BaseAdapter,
367
- isBooleanAttr,
368
- parseExpression as parseExpression3,
367
+ isBooleanAttr as isBooleanAttr2,
368
+ parseExpression as parseExpression4,
369
369
  stringifyParsedExpr as stringifyParsedExpr2,
370
370
  parseStyleObjectEntries,
371
371
  isSupported,
@@ -383,7 +383,13 @@ import {
383
383
  prepareLoweringMatchers,
384
384
  envSignalReaderFor,
385
385
  computeSsrSeedPlan,
386
- isStringConcatBinary
386
+ isStringConcatBinary,
387
+ isDangerousInnerHtmlAttr,
388
+ resolveDangerousInnerHtml,
389
+ dangerousInnerHtmlMetacharViolation,
390
+ dangerousInnerHtmlDiagnostic,
391
+ collectLoopBoundNames as collectLoopBoundNames2,
392
+ evaluateStaticLiteral as evaluateStaticLiteral3
387
393
  } from "@barefootjs/jsx";
388
394
  import { findInterpolationEnd } from "@barefootjs/jsx/scanner";
389
395
  import { BF_REGION, escapeHtml } from "@barefootjs/shared";
@@ -689,6 +695,7 @@ class CompileState {
689
695
  restPropsName = null;
690
696
  moduleStringConsts = new Map;
691
697
  localConstants = [];
698
+ staticLoopSourceBoundNames = new Set;
692
699
  localHelperNames = new Set;
693
700
  currentMemos = [];
694
701
  currentTypeDefinitions = [];
@@ -699,6 +706,8 @@ class CompileState {
699
706
  hoistedMemoLocals = new Map;
700
707
  loweringMatchers = [];
701
708
  nillablePropNames = new Set;
709
+ nullishConsumedPropNames = new Set;
710
+ omittableAttrConsumedPropNames = new Set;
702
711
  stringValueNames = new Set;
703
712
  rootScopeNodes = new Set;
704
713
  memoBackedLoopSlice = new Map;
@@ -857,6 +866,178 @@ function collectNestedComponents(node, result) {
857
866
  }
858
867
  }
859
868
 
869
+ // src/adapter/analysis/static-child-loop-bake.ts
870
+ import { evaluateStaticLiteral, parseExpression, resolveStaticLoopSource } from "@barefootjs/jsx";
871
+ function scalarToGoLiteral(value) {
872
+ if (typeof value === "string")
873
+ return `"${escapeGoString(value)}"`;
874
+ if (typeof value === "number")
875
+ return String(value);
876
+ if (typeof value === "boolean")
877
+ return value ? "true" : "false";
878
+ return null;
879
+ }
880
+ function analyzeBakeableStaticChildLoop(nested, localConstants, opts) {
881
+ if (!nested.loopParam || /^[{[]/.test(nested.loopParam))
882
+ return null;
883
+ const staticItemsResult = resolveStaticLoopSource(nested.loopArrayParsed, localConstants, opts);
884
+ if (staticItemsResult === null)
885
+ return null;
886
+ const items = [];
887
+ for (const item of staticItemsResult) {
888
+ const bindings = new Map([[nested.loopParam, item]]);
889
+ const inputFields = [];
890
+ for (const prop of nested.props) {
891
+ if (prop.isEventHandler)
892
+ continue;
893
+ if (prop.name.includes("-"))
894
+ continue;
895
+ const resolved = resolvePropValue(prop.value, bindings);
896
+ if (resolved === undefined)
897
+ return null;
898
+ const goValue = scalarToGoLiteral(resolved);
899
+ if (goValue === null)
900
+ return null;
901
+ inputFields.push({ goField: capitalizeFieldName(prop.name), goValue });
902
+ }
903
+ let dataKey = null;
904
+ if (nested.loopKey) {
905
+ const keyExpr = parseExpression(nested.loopKey);
906
+ const keyResolved = evaluateStaticLiteral(keyExpr, bindings);
907
+ if (keyResolved === null)
908
+ return null;
909
+ dataKey = String(keyResolved.value);
910
+ }
911
+ items.push({ inputFields, dataKey });
912
+ }
913
+ return { items };
914
+ }
915
+ function resolvePropValue(value, bindings) {
916
+ switch (value.kind) {
917
+ case "literal":
918
+ return value.value;
919
+ case "boolean-shorthand":
920
+ case "boolean-attr":
921
+ return true;
922
+ case "expression": {
923
+ if (!value.parsed)
924
+ return;
925
+ const resolved = evaluateStaticLiteral(value.parsed, bindings);
926
+ return resolved === null ? undefined : resolved.value;
927
+ }
928
+ default:
929
+ return;
930
+ }
931
+ }
932
+
933
+ // src/adapter/analysis/static-element-loop-bake.ts
934
+ import {
935
+ evaluateStaticLiteral as evaluateStaticLiteral2,
936
+ resolveStaticLoopSource as resolveStaticLoopSource2
937
+ } from "@barefootjs/jsx";
938
+ var ALLOWED_ATTR_EXPRESSION_KINDS = new Set([
939
+ "identifier",
940
+ "member",
941
+ "index-access",
942
+ "literal"
943
+ ]);
944
+ function analyzeBakeableStaticElementLoop(loop, localConstants, opts) {
945
+ if (loop.childComponent)
946
+ return null;
947
+ if (loop.method === "flatMap" || loop.flatMapCallback)
948
+ return null;
949
+ if (!loop.param || /^[{[]/.test(loop.param))
950
+ return null;
951
+ if (loop.index && loop.index !== "_")
952
+ return null;
953
+ if (loop.paramBindings && loop.paramBindings.length > 0)
954
+ return null;
955
+ if (loop.filterPredicate || loop.sortComparator)
956
+ return null;
957
+ if (loop.iterationShape || loop.objectIteration)
958
+ return null;
959
+ if (loop.bodyIsMultiRoot || loop.bodyIsItemConditional)
960
+ return null;
961
+ if (!isFoldableTree(loop.children))
962
+ return null;
963
+ const items = resolveStaticLoopSource2(loop.arrayParsed, localConstants, opts);
964
+ if (items === null)
965
+ return null;
966
+ for (const item of items) {
967
+ const bindings = new Map([[loop.param, item]]);
968
+ if (!allExpressionsFoldFor(loop.children, bindings))
969
+ return null;
970
+ }
971
+ return { items };
972
+ }
973
+ function isFoldableTree(nodes) {
974
+ for (const node of nodes) {
975
+ switch (node.type) {
976
+ case "text":
977
+ case "expression":
978
+ continue;
979
+ case "element":
980
+ if (!isFoldableAttrs(node))
981
+ return false;
982
+ if (!isFoldableTree(node.children))
983
+ return false;
984
+ continue;
985
+ default:
986
+ return false;
987
+ }
988
+ }
989
+ return true;
990
+ }
991
+ function isFoldableAttrs(element) {
992
+ for (const attr of element.attrs) {
993
+ if (attr.clientOnly)
994
+ continue;
995
+ switch (attr.value.kind) {
996
+ case "literal":
997
+ case "boolean-attr":
998
+ case "boolean-shorthand":
999
+ continue;
1000
+ case "expression":
1001
+ if (!attr.value.parsed || !ALLOWED_ATTR_EXPRESSION_KINDS.has(attr.value.parsed.kind))
1002
+ return false;
1003
+ continue;
1004
+ default:
1005
+ return false;
1006
+ }
1007
+ }
1008
+ return true;
1009
+ }
1010
+ function allExpressionsFoldFor(nodes, bindings) {
1011
+ for (const node of nodes) {
1012
+ if (node.type === "expression") {
1013
+ if (node.clientOnly)
1014
+ continue;
1015
+ if (!node.parsed || !resolvesToScalar(node.parsed, bindings))
1016
+ return false;
1017
+ continue;
1018
+ }
1019
+ if (node.type === "element") {
1020
+ for (const attr of node.attrs) {
1021
+ if (attr.clientOnly)
1022
+ continue;
1023
+ if (attr.value.kind !== "expression")
1024
+ continue;
1025
+ if (!attr.value.parsed || !resolvesToScalar(attr.value.parsed, bindings))
1026
+ return false;
1027
+ }
1028
+ if (!allExpressionsFoldFor(node.children, bindings))
1029
+ return false;
1030
+ }
1031
+ }
1032
+ return true;
1033
+ }
1034
+ function resolvesToScalar(expr, bindings) {
1035
+ const resolved = evaluateStaticLiteral2(expr, bindings);
1036
+ if (resolved === null)
1037
+ return false;
1038
+ return scalarToGoLiteral(resolved.value) !== null;
1039
+ }
1040
+
860
1041
  // src/adapter/expr/helper-inline.ts
861
1042
  function inlineLocalHelperCall(ctx, jsExpr, callParsed) {
862
1043
  if (ctx.state.localHelperNames.size === 0)
@@ -1044,7 +1225,7 @@ function forEachValueChild(n, visit) {
1044
1225
 
1045
1226
  // src/adapter/expr/url-builder.ts
1046
1227
  import {
1047
- parseExpression,
1228
+ parseExpression as parseExpression2,
1048
1229
  stringifyParsedExpr,
1049
1230
  isValidHelperId
1050
1231
  } from "@barefootjs/jsx";
@@ -1077,7 +1258,7 @@ function lowerRegisteredCall(ctx, jsExpr, preParsed) {
1077
1258
  if (!call) {
1078
1259
  if (!/^\s*[A-Za-z_$][\w$]*\s*\(/.test(jsExpr))
1079
1260
  return null;
1080
- const parsed = parseExpression(jsExpr);
1261
+ const parsed = parseExpression2(jsExpr);
1081
1262
  if (parsed.kind !== "call")
1082
1263
  return null;
1083
1264
  call = parsed;
@@ -1276,7 +1457,7 @@ function convertInitialValue(ctx, value, typeInfo, propsParams, preParsed) {
1276
1457
  return `in.${capitalizeFieldName(value)}`;
1277
1458
  }
1278
1459
  }
1279
- const propName = ctx.extractPropNameFromInitialValue(value);
1460
+ const propName = ctx.extractPropNameFromInitialValue(value, preParsed);
1280
1461
  if (propName && propsParams?.some((p) => p.name === propName)) {
1281
1462
  return `in.${capitalizeFieldName(propName)}`;
1282
1463
  }
@@ -2171,7 +2352,7 @@ function propsAccessNameFromParsed2(ctx, node) {
2171
2352
 
2172
2353
  // src/adapter/spread/spread-codegen.ts
2173
2354
  import ts2 from "typescript";
2174
- import { parseExpression as parseExpression2, parseRecordIndexAccess } from "@barefootjs/jsx";
2355
+ import { parseExpression as parseExpression3, parseRecordIndexAccess } from "@barefootjs/jsx";
2175
2356
  function collectSpreadSlots(ctx, node) {
2176
2357
  const result = [];
2177
2358
  collectSpreadSlotsRecursive(ctx, node, result);
@@ -2278,7 +2459,7 @@ function parsedObjectLiteralToGoMap(parsed) {
2278
2459
  }
2279
2460
  function buildSpreadInitializer(ctx, spreadExpr, ir, parsed) {
2280
2461
  const trimmed = spreadExpr.trim();
2281
- const conditionalTree = parsed ?? parseExpression2(trimmed);
2462
+ const conditionalTree = parsed ?? parseExpression3(trimmed);
2282
2463
  const conditional = buildConditionalSpreadInitializer(ctx, conditionalTree, ir);
2283
2464
  if (conditional !== undefined)
2284
2465
  return conditional;
@@ -2309,7 +2490,7 @@ function buildSpreadInitializer(ctx, spreadExpr, ir, parsed) {
2309
2490
  if (localConst?.value !== undefined) {
2310
2491
  const initTrimmed = localConst.value.trim();
2311
2492
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(initTrimmed)) {
2312
- const resolved = buildConditionalSpreadInitializer(ctx, parseExpression2(initTrimmed), ir);
2493
+ const resolved = buildConditionalSpreadInitializer(ctx, parseExpression3(initTrimmed), ir);
2313
2494
  if (resolved)
2314
2495
  return resolved;
2315
2496
  if (resolved === null)
@@ -2420,11 +2601,12 @@ function recordIndexAccessToGoMap(ctx, val, ir) {
2420
2601
  }
2421
2602
 
2422
2603
  // src/adapter/props/prop-types.ts
2604
+ import { isBooleanAttr } from "@barefootjs/jsx";
2423
2605
  function buildPropTypeOverrides(ctx, ir) {
2424
2606
  const overrides = new Map;
2425
2607
  for (const signal of ir.metadata.signals) {
2426
2608
  const propNames = [signal.initialValue];
2427
- const extracted = ctx.extractPropNameFromInitialValue(signal.initialValue);
2609
+ const extracted = ctx.extractPropNameFromInitialValue(signal.initialValue, signal.parsed);
2428
2610
  if (extracted)
2429
2611
  propNames.push(extracted);
2430
2612
  for (const propName of propNames) {
@@ -2481,11 +2663,96 @@ function collectToFixedPropNames(root) {
2481
2663
  walk(root);
2482
2664
  return names;
2483
2665
  }
2666
+ var NULLISH_SCALAR_GO_TYPES = new Set(["string", "int", "float64", "bool"]);
2667
+ function collectNullishConsumedPropNames(ctx, ir) {
2668
+ const names = new Set;
2669
+ const optionalParams = new Set(ir.metadata.propsParams.filter((p) => p.optional && p.defaultValue == null).map((p) => p.name));
2670
+ if (optionalParams.size === 0)
2671
+ return names;
2672
+ const propsObject = ctx.state.propsObjectName;
2673
+ const propNameOfLeft = (left) => {
2674
+ if (left.kind === "identifier")
2675
+ return left.name;
2676
+ if (left.kind === "member" && !left.computed && left.object.kind === "identifier" && left.object.name === propsObject) {
2677
+ return left.property;
2678
+ }
2679
+ return null;
2680
+ };
2681
+ const isZeroEquivalentLiteral = (right) => right.kind === "literal" && (right.value === "" || right.value === false || right.value === null || right.literalType === "number" && Number(right.value) === 0);
2682
+ const walk = (node) => {
2683
+ if (!node || typeof node !== "object")
2684
+ return;
2685
+ if (Array.isArray(node)) {
2686
+ for (const item of node)
2687
+ walk(item);
2688
+ return;
2689
+ }
2690
+ const rec = node;
2691
+ if (rec.kind === "logical" && rec.op === "??" && rec.left && rec.right) {
2692
+ const propName = propNameOfLeft(rec.left);
2693
+ if (propName && optionalParams.has(propName) && !isZeroEquivalentLiteral(rec.right)) {
2694
+ names.add(propName);
2695
+ }
2696
+ }
2697
+ for (const value of Object.values(rec))
2698
+ walk(value);
2699
+ };
2700
+ walk(ir.root);
2701
+ for (const signal of ir.metadata.signals) {
2702
+ const match = ctx.extractPropFallback(signal.initialValue, signal.parsed);
2703
+ if (!match || !optionalParams.has(match.propName))
2704
+ continue;
2705
+ const f = match.goFallback;
2706
+ if (f === '""' || f === "false" || f === "nil" || Number(f) === 0)
2707
+ continue;
2708
+ names.add(match.propName);
2709
+ }
2710
+ return names;
2711
+ }
2712
+ function collectOmittableAttrConsumedPropNames(ctx, ir) {
2713
+ const names = new Set;
2714
+ const optionalParams = new Set(ir.metadata.propsParams.filter((p) => p.optional && p.defaultValue == null).map((p) => p.name));
2715
+ if (optionalParams.size === 0)
2716
+ return names;
2717
+ const propsObject = ctx.state.propsObjectName;
2718
+ const walk = (node) => {
2719
+ if (!node || typeof node !== "object")
2720
+ return;
2721
+ if (Array.isArray(node)) {
2722
+ for (const item of node)
2723
+ walk(item);
2724
+ return;
2725
+ }
2726
+ const rec = node;
2727
+ if (rec.type === "element" && Array.isArray(rec.attrs)) {
2728
+ for (const attr of rec.attrs) {
2729
+ if (attr.name === "class" || attr.name === "className" || attr.name === "style")
2730
+ continue;
2731
+ if (isBooleanAttr(attr.name))
2732
+ continue;
2733
+ if (attr.value?.kind !== "expression" || attr.value.presenceOrUndefined)
2734
+ continue;
2735
+ const bareId = String(attr.value.expr ?? "").trim();
2736
+ const propName = propsObject && bareId.startsWith(`${propsObject}.`) ? bareId.slice(propsObject.length + 1) : bareId;
2737
+ if (/^[A-Za-z_$][\w$]*$/.test(propName) && optionalParams.has(propName)) {
2738
+ names.add(propName);
2739
+ }
2740
+ }
2741
+ }
2742
+ for (const value of Object.values(rec))
2743
+ walk(value);
2744
+ };
2745
+ walk(ir.root);
2746
+ return names;
2747
+ }
2484
2748
  function resolvePropGoType(ctx, param, propTypeOverrides) {
2485
2749
  const base = propTypeOverrides.get(param.name) ?? typeInfoToGo(ctx, param.type, param.defaultValue);
2486
2750
  if (param.optional && ctx.state.localStructFields.has(base)) {
2487
2751
  return "map[string]interface{}";
2488
2752
  }
2753
+ 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)) {
2754
+ return "interface{}";
2755
+ }
2489
2756
  return base;
2490
2757
  }
2491
2758
  function collectNillablePropNames(ctx, ir) {
@@ -2500,6 +2767,7 @@ function collectNillablePropNames(ctx, ir) {
2500
2767
  }
2501
2768
 
2502
2769
  // src/adapter/props/prop-classes.ts
2770
+ import { collectLoopBoundNames } from "@barefootjs/jsx";
2503
2771
  function isStringTypeInfo(type) {
2504
2772
  return type.kind === "primitive" && type.primitive === "string";
2505
2773
  }
@@ -2520,6 +2788,13 @@ function collectStringValueNames(ir) {
2520
2788
  if (isStringTypeInfo(p.type))
2521
2789
  names.add(p.name);
2522
2790
  }
2791
+ for (const c of ir.metadata.localConstants) {
2792
+ if (c.type !== null && isStringTypeInfo(c.type) || isBareStringLiteral(c.value)) {
2793
+ names.add(c.name);
2794
+ }
2795
+ }
2796
+ for (const bound of collectLoopBoundNames(ir))
2797
+ names.delete(bound);
2523
2798
  return names;
2524
2799
  }
2525
2800
 
@@ -2556,14 +2831,15 @@ class GoTemplateAdapter extends BaseAdapter {
2556
2831
  state: this.state,
2557
2832
  convertExpressionToGo: (jsExpr, out, preParsed) => this.convertExpressionToGo(jsExpr, out, preParsed),
2558
2833
  convertConditionToGo: (jsCondition, preParsed) => this.convertConditionToGo(jsCondition, preParsed),
2559
- extractPropNameFromInitialValue: (initialValue) => this.extractPropNameFromInitialValue(initialValue),
2560
- extractPropFallback: (initialValue) => this.extractPropFallback(initialValue),
2834
+ extractPropNameFromInitialValue: (initialValue, preParsed) => this.extractPropNameFromInitialValue(initialValue, preParsed),
2835
+ extractPropFallback: (initialValue, preParsed) => this.extractPropFallback(initialValue, preParsed),
2561
2836
  resolveModuleStringConst: (name) => this.resolveModuleStringConst(name)
2562
2837
  };
2563
2838
  get errors() {
2564
2839
  return this.state.errors;
2565
2840
  }
2566
2841
  inLoop = false;
2842
+ bakedStaticChildLoopCache = new Map;
2567
2843
  loopParamStack = [];
2568
2844
  loopKeyDepthStack = [];
2569
2845
  loopScalarItemStack = [];
@@ -2571,6 +2847,8 @@ class GoTemplateAdapter extends BaseAdapter {
2571
2847
  loopVarRefCount = new Map;
2572
2848
  loopBindingStack = [];
2573
2849
  loopRestExcludeStack = [];
2850
+ staticLoopItemStack = [];
2851
+ staticLoopBakeFailed = false;
2574
2852
  childComponentShapes = new Map;
2575
2853
  childContextConsumers = new Map;
2576
2854
  constructor(options = {}) {
@@ -2586,6 +2864,8 @@ class GoTemplateAdapter extends BaseAdapter {
2586
2864
  this.state.restPropsName = ir.metadata.restPropsName ?? null;
2587
2865
  this.state.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
2588
2866
  this.state.localConstants = ir.metadata.localConstants ?? [];
2867
+ this.state.staticLoopSourceBoundNames = collectLoopBoundNames2(ir);
2868
+ this.bakedStaticChildLoopCache = new Map;
2589
2869
  this.state.localHelperNames = new Set(this.state.localConstants.filter((c) => !c.isModule && c.containsArrow).map((c) => c.name));
2590
2870
  this.state.currentMemos = ir.metadata.memos ?? [];
2591
2871
  this.state.currentTypeDefinitions = ir.metadata.typeDefinitions ?? [];
@@ -2604,6 +2884,10 @@ class GoTemplateAdapter extends BaseAdapter {
2604
2884
  }
2605
2885
  this.state.loweringMatchers = prepareLoweringMatchers(ir.metadata);
2606
2886
  augmentInheritedPropAccesses(ir);
2887
+ this.buildLocalTypeTables(ir, ir.metadata.componentName);
2888
+ this.state.nullishConsumedPropNames = collectNullishConsumedPropNames(this.emitCtx, ir);
2889
+ this.state.omittableAttrConsumedPropNames = collectOmittableAttrConsumedPropNames(this.emitCtx, ir);
2890
+ this.state.nillablePropNames = collectNillablePropNames(this.emitCtx, ir);
2607
2891
  }
2608
2892
  generate(ir, options) {
2609
2893
  this.state.componentName = ir.metadata.componentName;
@@ -2612,7 +2896,6 @@ class GoTemplateAdapter extends BaseAdapter {
2612
2896
  this.state.templateVarCounter = 0;
2613
2897
  this.state.pendingChildrenDefines = [];
2614
2898
  this.primeCompileState(ir);
2615
- this.state.nillablePropNames = collectNillablePropNames(this.emitCtx, ir);
2616
2899
  this.state.stringValueNames = collectStringValueNames(ir);
2617
2900
  if (!options?.siblingTemplatesRegistered) {
2618
2901
  this.checkImportedLoopChildComponents(ir);
@@ -2967,6 +3250,8 @@ ${goFields.join(`
2967
3250
  lines.push(` ${fieldName} ${goType}`);
2968
3251
  }
2969
3252
  for (const nested of inputNested) {
3253
+ if (nested.loopMarkerId && this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey))
3254
+ continue;
2970
3255
  lines.push(` ${nested.name}s []${nested.name}Input`);
2971
3256
  }
2972
3257
  const takenInput = new Set(ir.metadata.propsParams.map((p) => capitalizeFieldName(p.name)));
@@ -3098,6 +3383,21 @@ ${goFields.join(`
3098
3383
  const staticWithoutBody = staticNested.filter((n) => !n.bodyChildren || n.bodyChildren.length === 0);
3099
3384
  for (const nested of staticWithoutBody) {
3100
3385
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
3386
+ const baked = nested.loopMarkerId ? this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey) : null;
3387
+ if (baked) {
3388
+ lines.push(` ${varName} := make([]${nested.name}Props, ${baked.items.length})`);
3389
+ baked.items.forEach((item, i) => {
3390
+ const fields = item.inputFields.map((f) => `${f.goField}: ${f.goValue}`).join(", ");
3391
+ lines.push(` ${varName}[${i}] = New${nested.name}Props(${nested.name}Input{${fields}})`);
3392
+ lines.push(` ${varName}[${i}].BfParent = scopeID`);
3393
+ lines.push(` ${varName}[${i}].BfMount = "${nested.slotId}"`);
3394
+ if (item.dataKey !== null) {
3395
+ lines.push(` ${varName}[${i}].BfDataKey = ${JSON.stringify(item.dataKey)}`);
3396
+ }
3397
+ });
3398
+ lines.push("");
3399
+ continue;
3400
+ }
3101
3401
  lines.push(` ${varName} := make([]${nested.name}Props, len(in.${nested.name}s))`);
3102
3402
  lines.push(` for i, item := range in.${nested.name}s {`);
3103
3403
  lines.push(` ${varName}[i] = New${nested.name}Props(item)`);
@@ -3114,10 +3414,18 @@ ${goFields.join(`
3114
3414
  this.emitStaticBodyWrappers(lines, ir, componentName, staticWithBody, emittedWrapperVars);
3115
3415
  const propFallbackVars = this.collectPropFallbackVars(ir);
3116
3416
  for (const [, info] of propFallbackVars) {
3117
- lines.push(` ${info.varName} := in.${info.fieldName}`);
3118
- lines.push(` if ${info.varName} == ${info.zeroLiteral} {`);
3119
- lines.push(` ${info.varName} = ${info.goFallback}`);
3120
- lines.push(` }`);
3417
+ if (info.assertType) {
3418
+ const deref = info.assertType === "int" ? `bf.ToInt(in.${info.fieldName})` : info.assertType === "float64" ? `bf.ToFloat64(in.${info.fieldName})` : `in.${info.fieldName}.(${info.assertType})`;
3419
+ lines.push(` var ${info.varName} ${info.assertType} = ${info.goFallback}`);
3420
+ lines.push(` if in.${info.fieldName} != nil {`);
3421
+ lines.push(` ${info.varName} = ${deref}`);
3422
+ lines.push(` }`);
3423
+ } else {
3424
+ lines.push(` ${info.varName} := in.${info.fieldName}`);
3425
+ lines.push(` if ${info.varName} == ${info.zeroLiteral} {`);
3426
+ lines.push(` ${info.varName} = ${info.goFallback}`);
3427
+ lines.push(` }`);
3428
+ }
3121
3429
  }
3122
3430
  if (propFallbackVars.size > 0)
3123
3431
  lines.push("");
@@ -3197,7 +3505,7 @@ ${goFields.join(`
3197
3505
  const fieldName = capitalizeFieldName(signal.getter);
3198
3506
  if (propFieldNames.has(fieldName))
3199
3507
  continue;
3200
- const fallbackMatch = this.extractPropFallback(signal.initialValue);
3508
+ const fallbackMatch = this.extractPropFallback(signal.initialValue, signal.parsed);
3201
3509
  const hoisted = fallbackMatch ? propFallbackVars.get(fallbackMatch.propName) : undefined;
3202
3510
  if (hoisted) {
3203
3511
  lines.push(` ${fieldName}: ${hoisted.varName},`);
@@ -4043,8 +4351,9 @@ ${goFields.join(`
4043
4351
  for (const nested of findNestedComponents(ir.root)) {
4044
4352
  localTaken.add(`${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`);
4045
4353
  }
4354
+ const propTypeOverrides = buildPropTypeOverrides(this.emitCtx, ir);
4046
4355
  for (const signal of ir.metadata.signals) {
4047
- const match = this.extractPropFallback(signal.initialValue);
4356
+ const match = this.extractPropFallback(signal.initialValue, signal.parsed);
4048
4357
  if (!match)
4049
4358
  continue;
4050
4359
  if (result.has(match.propName))
@@ -4055,6 +4364,8 @@ ${goFields.join(`
4055
4364
  if (goPropDefault(param.defaultValue) !== null)
4056
4365
  continue;
4057
4366
  const fieldName = capitalizeFieldName(match.propName);
4367
+ const concreteType = propTypeOverrides.get(param.name) ?? typeInfoToGo(this.emitCtx, param.type, param.defaultValue);
4368
+ const nullishLowered = NULLISH_SCALAR_GO_TYPES.has(concreteType) && resolvePropGoType(this.emitCtx, param, propTypeOverrides) === "interface{}";
4058
4369
  let zeroLiteral;
4059
4370
  if (match.goFallback === "true" || match.goFallback === "false") {
4060
4371
  zeroLiteral = "false";
@@ -4065,20 +4376,31 @@ ${goFields.join(`
4065
4376
  } else {
4066
4377
  continue;
4067
4378
  }
4068
- if (match.goFallback === zeroLiteral)
4069
- continue;
4070
- if (zeroLiteral === "0" && Number(match.goFallback) === 0)
4071
- continue;
4379
+ if (!nullishLowered) {
4380
+ if (match.goFallback === zeroLiteral)
4381
+ continue;
4382
+ if (zeroLiteral === "0" && Number(match.goFallback) === 0)
4383
+ continue;
4384
+ }
4072
4385
  let varName = match.propName;
4073
4386
  while (localTaken.has(varName) || GO_KEYWORDS.has(varName)) {
4074
4387
  varName += "_";
4075
4388
  }
4076
4389
  localTaken.add(varName);
4077
- result.set(match.propName, { varName, fieldName, goFallback: match.goFallback, zeroLiteral });
4390
+ result.set(match.propName, {
4391
+ varName,
4392
+ fieldName,
4393
+ goFallback: match.goFallback,
4394
+ zeroLiteral,
4395
+ ...nullishLowered ? { assertType: concreteType } : {}
4396
+ });
4078
4397
  }
4079
4398
  return result;
4080
4399
  }
4081
- extractPropFallback(initialValue) {
4400
+ extractPropFallback(initialValue, preParsed) {
4401
+ const structural = preParsed ? this.extractPropFallbackFromParsed(preParsed) : null;
4402
+ if (structural)
4403
+ return structural;
4082
4404
  if (!this.state.propsObjectName)
4083
4405
  return null;
4084
4406
  const trimmed = initialValue.trim();
@@ -4092,9 +4414,38 @@ ${goFields.join(`
4092
4414
  return null;
4093
4415
  return { propName: m[1], goFallback };
4094
4416
  }
4095
- extractPropNameFromInitialValue(initialValue) {
4096
- if (!this.state.propsObjectName)
4417
+ extractPropFallbackFromParsed(preParsed) {
4418
+ if (preParsed.kind !== "logical" || preParsed.op !== "??")
4419
+ return null;
4420
+ const left = preParsed.left;
4421
+ 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;
4422
+ if (!propName)
4423
+ return null;
4424
+ let right = preParsed.right;
4425
+ let negate = "";
4426
+ if (right.kind === "unary" && right.op === "-") {
4427
+ negate = "-";
4428
+ right = right.argument;
4429
+ }
4430
+ if (right.kind !== "literal")
4431
+ return null;
4432
+ if (negate && right.literalType !== "number")
4433
+ return null;
4434
+ if (right.literalType === "string") {
4435
+ return { propName, goFallback: JSON.stringify(right.value) };
4436
+ }
4437
+ const goFallback = goPropDefault(negate + (right.raw ?? String(right.value)));
4438
+ if (goFallback === null)
4439
+ return null;
4440
+ return { propName, goFallback };
4441
+ }
4442
+ extractPropNameFromInitialValue(initialValue, preParsed) {
4443
+ if (!this.state.propsObjectName) {
4444
+ if (preParsed?.kind === "logical" && (preParsed.op === "??" || preParsed.op === "||") && preParsed.left.kind === "identifier") {
4445
+ return preParsed.left.name;
4446
+ }
4097
4447
  return null;
4448
+ }
4098
4449
  const trimmed = initialValue.trim();
4099
4450
  const name = this.state.propsObjectName;
4100
4451
  const direct = new RegExp(`^${name}\\.(\\w+)(?:\\s*(?:\\?\\?|\\|\\|)\\s*.+)?$`);
@@ -4151,7 +4502,8 @@ ${goFields.join(`
4151
4502
  renderElement(element) {
4152
4503
  const tag = element.tag;
4153
4504
  const attrs = this.renderAttributes(element);
4154
- const children = this.renderChildren(element.children);
4505
+ const dangerousHtml = this.renderDangerousInnerHtml(element);
4506
+ const children = dangerousHtml !== null ? dangerousHtml : this.renderChildren(element.children);
4155
4507
  let hydrationAttrs = "";
4156
4508
  if (element.needsScope) {
4157
4509
  hydrationAttrs += ` ${this.renderScopeMarker(".ScopeID")}`;
@@ -4186,6 +4538,22 @@ ${goFields.join(`
4186
4538
  }
4187
4539
  return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
4188
4540
  }
4541
+ renderDangerousInnerHtml(element) {
4542
+ const resolution = resolveDangerousInnerHtml(element);
4543
+ if (!resolution)
4544
+ return null;
4545
+ if (resolution.kind === "dynamic") {
4546
+ this.errors.push(dangerousInnerHtmlDiagnostic(resolution.expr, resolution.loc));
4547
+ return "";
4548
+ }
4549
+ const violation = dangerousInnerHtmlMetacharViolation(resolution.html, this.name);
4550
+ if (violation) {
4551
+ const attr = element.attrs.find(isDangerousInnerHtmlAttr);
4552
+ this.errors.push(dangerousInnerHtmlDiagnostic(`{ __html: ${JSON.stringify(resolution.html)} }`, attr.loc, violation));
4553
+ return "";
4554
+ }
4555
+ return resolution.html;
4556
+ }
4189
4557
  renderExpression(expr) {
4190
4558
  if (expr.clientOnly) {
4191
4559
  if (expr.slotId) {
@@ -4489,8 +4857,20 @@ ${goFields.join(`
4489
4857
  const wrapRight = wrapIfMultiToken(emit(right));
4490
4858
  if (op === "&&")
4491
4859
  return `and ${wrapLeft} ${wrapRight}`;
4860
+ if (op === "??" && this.nillablePropNameOf(left) !== null) {
4861
+ return `bf_nullish ${wrapLeft} ${wrapRight}`;
4862
+ }
4492
4863
  return `or ${wrapLeft} ${wrapRight}`;
4493
4864
  }
4865
+ nillablePropNameOf(expr) {
4866
+ let name = null;
4867
+ if (expr.kind === "identifier") {
4868
+ name = expr.name;
4869
+ } else if (expr.kind === "member" && !expr.computed && expr.object.kind === "identifier" && expr.object.name === this.state.propsObjectName) {
4870
+ name = expr.property;
4871
+ }
4872
+ return name !== null && this.state.nullishConsumedPropNames.has(name) && this.state.nillablePropNames.has(name) ? name : null;
4873
+ }
4494
4874
  conditional(test, consequent, alternate, emit) {
4495
4875
  const t = emit(test);
4496
4876
  const c = this.renderConditionalBranch(consequent);
@@ -4919,8 +5299,8 @@ ${goFields.join(`
4919
5299
  const value = negated ? "false" : "true";
4920
5300
  return `len (bf_filter ${arrayExpr} "${field}" ${value})`;
4921
5301
  }
4922
- renderPredicateCondition(pred, param) {
4923
- return this.renderFilterExpr(pred, param);
5302
+ renderPredicateCondition(pred, param, datumField) {
5303
+ return this.renderFilterExpr(pred, param, new Map, datumField ?? undefined);
4924
5304
  }
4925
5305
  needsParens(expr) {
4926
5306
  return expr.kind === "logical" || expr.kind === "unary" || expr.kind === "conditional";
@@ -4941,21 +5321,23 @@ ${goFields.join(`
4941
5321
  }
4942
5322
  return null;
4943
5323
  }
4944
- renderFilterExpr(expr, param, localVarMap = new Map) {
5324
+ renderFilterExpr(expr, param, localVarMap = new Map, datumField) {
4945
5325
  if (this.filterExprDepth === 0)
4946
5326
  this.filterExprUnsupported = false;
4947
5327
  this.filterExprDepth++;
4948
5328
  try {
4949
- return this.renderFilterExprNode(expr, param, localVarMap);
5329
+ return this.renderFilterExprNode(expr, param, localVarMap, datumField);
4950
5330
  } finally {
4951
5331
  this.filterExprDepth--;
4952
5332
  }
4953
5333
  }
4954
- renderFilterExprNode(expr, param, localVarMap) {
5334
+ renderFilterExprNode(expr, param, localVarMap, datumField) {
5335
+ const paramPrefix = datumField ? `.${datumField}` : "";
5336
+ const paramDot = paramPrefix || ".";
4955
5337
  switch (expr.kind) {
4956
5338
  case "identifier": {
4957
5339
  if (expr.name === param) {
4958
- return ".";
5340
+ return paramDot;
4959
5341
  }
4960
5342
  const signal = localVarMap.get(expr.name);
4961
5343
  if (signal) {
@@ -4973,24 +5355,24 @@ ${goFields.join(`
4973
5355
  return String(expr.value);
4974
5356
  case "member": {
4975
5357
  if (expr.object.kind === "identifier" && expr.object.name === param) {
4976
- return `.${capitalizeFieldName(expr.property)}`;
5358
+ return `${paramPrefix}.${capitalizeFieldName(expr.property)}`;
4977
5359
  }
4978
5360
  if (expr.property === "length") {
4979
5361
  const innerHO = this.higherOrderShapeOf(expr.object);
4980
5362
  if (innerHO && innerHO.method === "filter") {
4981
- const lenExpr = this.renderFilterLengthExpr(innerHO, (e) => this.renderFilterExpr(e, param, localVarMap));
5363
+ const lenExpr = this.renderFilterLengthExpr(innerHO, (e) => this.renderFilterExpr(e, param, localVarMap, datumField));
4982
5364
  if (lenExpr)
4983
5365
  return `(${lenExpr})`;
4984
5366
  }
4985
5367
  }
4986
- const obj = this.renderFilterExpr(expr.object, param, localVarMap);
5368
+ const obj = this.renderFilterExpr(expr.object, param, localVarMap, datumField);
4987
5369
  if (this.filterExprUnsupported)
4988
5370
  return "false";
4989
5371
  return `${obj}.${capitalizeFieldName(expr.property)}`;
4990
5372
  }
4991
5373
  case "call": {
4992
5374
  if (expr.callee.kind === "member" && expr.callee.object.kind === "identifier" && expr.callee.object.name === param) {
4993
- return `.${capitalizeFieldName(expr.callee.property)}`;
5375
+ return `${paramPrefix}.${capitalizeFieldName(expr.callee.property)}`;
4994
5376
  }
4995
5377
  if (expr.callee.kind === "identifier" && expr.args.length === 0) {
4996
5378
  return `$.${capitalizeFieldName(expr.callee.name)}`;
@@ -4998,13 +5380,13 @@ ${goFields.join(`
4998
5380
  if (asCallbackMethodCall3(expr) !== null) {
4999
5381
  return this.refuseFilterExprNode(expr);
5000
5382
  }
5001
- const result = this.renderFilterExpr(expr.callee, param, localVarMap);
5383
+ const result = this.renderFilterExpr(expr.callee, param, localVarMap, datumField);
5002
5384
  if (this.filterExprUnsupported)
5003
5385
  return "false";
5004
5386
  return result;
5005
5387
  }
5006
5388
  case "unary": {
5007
- const arg = this.renderFilterExpr(expr.argument, param, localVarMap);
5389
+ const arg = this.renderFilterExpr(expr.argument, param, localVarMap, datumField);
5008
5390
  if (this.filterExprUnsupported)
5009
5391
  return "false";
5010
5392
  if (expr.op === "!") {
@@ -5017,10 +5399,10 @@ ${goFields.join(`
5017
5399
  return arg;
5018
5400
  }
5019
5401
  case "binary": {
5020
- const left = this.renderFilterExpr(expr.left, param, localVarMap);
5402
+ const left = this.renderFilterExpr(expr.left, param, localVarMap, datumField);
5021
5403
  if (this.filterExprUnsupported)
5022
5404
  return "false";
5023
- const right = this.renderFilterExpr(expr.right, param, localVarMap);
5405
+ const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField);
5024
5406
  if (this.filterExprUnsupported)
5025
5407
  return "false";
5026
5408
  switch (expr.op) {
@@ -5051,10 +5433,10 @@ ${goFields.join(`
5051
5433
  }
5052
5434
  }
5053
5435
  case "logical": {
5054
- const left = this.renderFilterExpr(expr.left, param, localVarMap);
5436
+ const left = this.renderFilterExpr(expr.left, param, localVarMap, datumField);
5055
5437
  if (this.filterExprUnsupported)
5056
5438
  return "false";
5057
- const right = this.renderFilterExpr(expr.right, param, localVarMap);
5439
+ const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField);
5058
5440
  if (this.filterExprUnsupported)
5059
5441
  return "false";
5060
5442
  if (expr.op === "&&") {
@@ -5122,11 +5504,22 @@ ${goFields.join(`
5122
5504
  if (trimmed === "null" || trimmed === "undefined") {
5123
5505
  return '""';
5124
5506
  }
5507
+ if (this.staticLoopItemStack.length > 0) {
5508
+ const top = this.staticLoopItemStack[this.staticLoopItemStack.length - 1];
5509
+ const parsedForBake = preParsed ?? parseExpression4(trimmed);
5510
+ const resolved = evaluateStaticLiteral3(parsedForBake, new Map([[top.param, top.item]]));
5511
+ const literal = resolved !== null ? scalarToGoLiteral(resolved.value) : null;
5512
+ if (literal !== null) {
5513
+ return literal;
5514
+ }
5515
+ this.staticLoopBakeFailed = true;
5516
+ return '""';
5517
+ }
5125
5518
  const staticIndexed = this.resolveStaticRecordLiteralIndex(trimmed);
5126
5519
  if (staticIndexed !== null) {
5127
5520
  return staticIndexed;
5128
5521
  }
5129
- if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
5522
+ if (!this.isLoopShadowedName(trimmed) && /^[A-Za-z_$][\w$]*$/.test(trimmed)) {
5130
5523
  const litConst = (this.state.localConstants ?? []).find((c) => c.name === trimmed);
5131
5524
  if (litConst?.value !== undefined) {
5132
5525
  const v = litConst.value.trim();
@@ -5144,7 +5537,7 @@ ${goFields.join(`
5144
5537
  if (inlined !== null) {
5145
5538
  return this.convertExpressionToGo(stringifyParsedExpr2(inlined), out, inlined);
5146
5539
  }
5147
- const parsed = preParsed ?? parseExpression3(trimmed);
5540
+ const parsed = preParsed ?? parseExpression4(trimmed);
5148
5541
  const support = isSupported(parsed);
5149
5542
  if (!support.supported) {
5150
5543
  this.state.errors.push({
@@ -5162,10 +5555,15 @@ ${goFields.join(`
5162
5555
  out.parsed = parsed;
5163
5556
  return this.renderParsedExpr(parsed);
5164
5557
  }
5558
+ isLoopShadowedName(name) {
5559
+ 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));
5560
+ }
5165
5561
  resolveStaticRecordLiteralIndex(jsExpr) {
5166
5562
  const m = /^([A-Za-z_$][\w$]*)\[\s*(?:'([^']*)'|"([^"]*)")\s*\]$/.exec(jsExpr) ?? /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/.exec(jsExpr);
5167
5563
  if (!m)
5168
5564
  return null;
5565
+ if (this.isLoopShadowedName(m[1]))
5566
+ return null;
5169
5567
  const key = m[2] ?? m[3];
5170
5568
  const constInfo = (this.state.localConstants ?? []).find((c) => c.name === m[1] && c.isModule);
5171
5569
  if (constInfo?.value === undefined)
@@ -5271,7 +5669,7 @@ ${goFields.join(`
5271
5669
  }
5272
5670
  convertConditionToGo(jsCondition, preParsed) {
5273
5671
  const trimmed = jsCondition.trim();
5274
- const parsed = preParsed ?? parseExpression3(trimmed);
5672
+ const parsed = preParsed ?? parseExpression4(trimmed);
5275
5673
  const support = isSupported(parsed);
5276
5674
  if (!support.supported) {
5277
5675
  this.state.errors.push({
@@ -5492,6 +5890,29 @@ ${goFields.join(`
5492
5890
  }
5493
5891
  return;
5494
5892
  }
5893
+ wrapperDatumField(loop) {
5894
+ if (!loop.childComponent)
5895
+ return null;
5896
+ for (const prop of loop.childComponent.props) {
5897
+ if (prop.isEventHandler)
5898
+ continue;
5899
+ if (prop.value.kind !== "expression")
5900
+ continue;
5901
+ const parsed = prop.value.parsed;
5902
+ const isBareParamRef = parsed ? parsed.kind === "identifier" && parsed.name === loop.param : prop.value.expr.trim() === loop.param;
5903
+ if (isBareParamRef)
5904
+ return capitalizeFieldName(prop.name);
5905
+ }
5906
+ return null;
5907
+ }
5908
+ getBakedStaticChildLoop(markerId, childComponent, arrayParsed, param, key) {
5909
+ if (this.bakedStaticChildLoopCache.has(markerId)) {
5910
+ return this.bakedStaticChildLoopCache.get(markerId) ?? null;
5911
+ }
5912
+ const result = analyzeBakeableStaticChildLoop({ props: childComponent.props, loopArrayParsed: arrayParsed, loopParam: param, loopKey: key }, this.state.localConstants, { isNameShadowed: (name) => this.state.staticLoopSourceBoundNames.has(name) });
5913
+ this.bakedStaticChildLoopCache.set(markerId, result);
5914
+ return result;
5915
+ }
5495
5916
  renderLoop(loop) {
5496
5917
  if (loop.clientOnly) {
5497
5918
  return `{{bfComment "loop:${loop.markerId}"}}{{bfComment "/loop:${loop.markerId}"}}`;
@@ -5512,8 +5933,13 @@ ${goFields.join(`
5512
5933
  }
5513
5934
  });
5514
5935
  }
5936
+ const bakedChildLoop = loop.childComponent ? this.getBakedStaticChildLoop(loop.markerId, loop.childComponent, loop.arrayParsed, loop.param, loop.key ?? undefined) : null;
5937
+ const bakedElementLoop = loop.childComponent ? null : analyzeBakeableStaticElementLoop(loop, this.state.localConstants, { isNameShadowed: (name) => this.state.staticLoopSourceBoundNames.has(name) });
5938
+ if (bakedElementLoop) {
5939
+ return this.renderUnrolledStaticElementLoop(loop, bakedElementLoop.items);
5940
+ }
5515
5941
  const arrayName = loop.array.trim();
5516
- if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
5942
+ if (bakedChildLoop === null && /^[A-Za-z_$][\w$]*$/.test(arrayName)) {
5517
5943
  const arrayConst = this.state.localConstants.find((c) => c.name === arrayName);
5518
5944
  if (arrayConst && !arrayConst.isModule && arrayConst.parsed && !this.isStringExpr(arrayConst.parsed, new Set)) {
5519
5945
  this.state.errors.push({
@@ -5527,7 +5953,7 @@ ${goFields.join(`
5527
5953
  });
5528
5954
  }
5529
5955
  }
5530
- let goArray = this.convertExpressionToGo(loop.array);
5956
+ let goArray = loop.childComponent ? "" : this.convertExpressionToGo(loop.array);
5531
5957
  const param = loop.param;
5532
5958
  let index = loop.index || "_";
5533
5959
  let rangeIndex = index;
@@ -5595,7 +6021,8 @@ ${goFields.join(`
5595
6021
  if (loop.filterPredicate) {
5596
6022
  let filterCond;
5597
6023
  if (loop.filterPredicate.predicate) {
5598
- filterCond = this.renderPredicateCondition(loop.filterPredicate.predicate, loop.filterPredicate.param);
6024
+ const datumField = this.wrapperDatumField(loop);
6025
+ filterCond = this.renderPredicateCondition(loop.filterPredicate.predicate, loop.filterPredicate.param, datumField);
5599
6026
  } else {
5600
6027
  filterCond = "true";
5601
6028
  }
@@ -5603,6 +6030,38 @@ ${goFields.join(`
5603
6030
  }
5604
6031
  return `{{bfComment "loop:${loop.markerId}"}}{{range $${rangeIndex}, $${rangeValue} := ${goArray}}}${itemMarker}${children}{{end}}{{bfComment "/loop:${loop.markerId}"}}`;
5605
6032
  }
6033
+ renderUnrolledStaticElementLoop(loop, items) {
6034
+ this.inLoop = true;
6035
+ this.loopWrapperStack.push(false);
6036
+ this.loopKeyDepthStack.push(loop.depth);
6037
+ this.loopScalarItemStack.push(this.scalarLiteralLoopGoType(loop.arrayParsed, loop.itemType) !== null);
6038
+ this.loopParamStack.push(loop.param);
6039
+ let body = "";
6040
+ for (const item of items) {
6041
+ this.staticLoopItemStack.push({ param: loop.param, item });
6042
+ body += this.renderChildren(loop.children);
6043
+ this.staticLoopItemStack.pop();
6044
+ if (this.staticLoopBakeFailed) {
6045
+ this.staticLoopBakeFailed = false;
6046
+ this.state.errors.push({
6047
+ code: "BF101",
6048
+ severity: "error",
6049
+ 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.`,
6050
+ loc: loop.loc ?? this.makeLoc(),
6051
+ suggestion: {
6052
+ 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."
6053
+ }
6054
+ });
6055
+ break;
6056
+ }
6057
+ }
6058
+ this.loopParamStack.pop();
6059
+ this.loopScalarItemStack.pop();
6060
+ this.loopKeyDepthStack.pop();
6061
+ this.loopWrapperStack.pop();
6062
+ this.inLoop = false;
6063
+ return `{{bfComment "loop:${loop.markerId}"}}${body}{{bfComment "/loop:${loop.markerId}"}}`;
6064
+ }
5606
6065
  loopItemMarker(loop) {
5607
6066
  if (loop.bodyIsMultiRoot)
5608
6067
  return `{{bfComment "bf-loop-i"}}`;
@@ -5717,12 +6176,12 @@ ${children}`;
5717
6176
  if (css !== null)
5718
6177
  return `style="${css}"`;
5719
6178
  }
5720
- if (isBooleanAttr(name) || value.presenceOrUndefined) {
6179
+ if (isBooleanAttr2(name) || value.presenceOrUndefined) {
5721
6180
  const { condition: goCond, preamble } = this.convertConditionToGo(value.expr, value.parsed);
5722
6181
  const body = name.startsWith("aria-") ? `${name}="true"` : name;
5723
6182
  return `${preamble}{{if ${goCond}}}${body}{{end}}`;
5724
6183
  }
5725
- const parsed = value.parsed ?? parseExpression3(value.expr.trim());
6184
+ const parsed = value.parsed ?? parseExpression4(value.expr.trim());
5726
6185
  if (parsed.kind === "conditional") {
5727
6186
  const undef = (e) => e.kind === "identifier" && (e.name === "undefined" || e.name === "null") || e.kind === "literal" && (e.value === null || e.value === undefined);
5728
6187
  const test = parsed.test;
@@ -5787,7 +6246,7 @@ ${children}`;
5787
6246
  if (!entries)
5788
6247
  return null;
5789
6248
  for (const e of entries) {
5790
- if (e.kind === "expr" && !isSupported(parseExpression3(e.expr)).supported)
6249
+ if (e.kind === "expr" && !isSupported(parseExpression4(e.expr)).supported)
5791
6250
  return null;
5792
6251
  }
5793
6252
  return entries.map((e) => e.kind === "literal" ? `${this.escapeAttrText(e.cssKey)}:${this.escapeAttrText(e.value)}` : `${this.escapeAttrText(e.cssKey)}:{{${this.convertExpressionToGo(e.expr)}}}`).join(";");
@@ -5797,6 +6256,8 @@ ${children}`;
5797
6256
  for (const attr of element.attrs) {
5798
6257
  if (attr.clientOnly)
5799
6258
  continue;
6259
+ if (isDangerousInnerHtmlAttr(attr))
6260
+ continue;
5800
6261
  let attrName;
5801
6262
  if (attr.name === "className")
5802
6263
  attrName = "class";