@barefootjs/go-template 0.18.5 → 0.18.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/build.js CHANGED
@@ -365,7 +365,7 @@ var init_path = __esm(() => {
365
365
  import {
366
366
  BaseAdapter,
367
367
  isBooleanAttr,
368
- parseExpression as parseExpression3,
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 = [];
@@ -857,6 +864,178 @@ function collectNestedComponents(node, result) {
857
864
  }
858
865
  }
859
866
 
867
+ // src/adapter/analysis/static-child-loop-bake.ts
868
+ import { evaluateStaticLiteral, parseExpression, resolveStaticLoopSource } from "@barefootjs/jsx";
869
+ function scalarToGoLiteral(value) {
870
+ if (typeof value === "string")
871
+ return `"${escapeGoString(value)}"`;
872
+ if (typeof value === "number")
873
+ return String(value);
874
+ if (typeof value === "boolean")
875
+ return value ? "true" : "false";
876
+ return null;
877
+ }
878
+ function analyzeBakeableStaticChildLoop(nested, localConstants, opts) {
879
+ if (!nested.loopParam || /^[{[]/.test(nested.loopParam))
880
+ return null;
881
+ const staticItemsResult = resolveStaticLoopSource(nested.loopArrayParsed, localConstants, opts);
882
+ if (staticItemsResult === null)
883
+ return null;
884
+ const items = [];
885
+ for (const item of staticItemsResult) {
886
+ const bindings = new Map([[nested.loopParam, item]]);
887
+ const inputFields = [];
888
+ for (const prop of nested.props) {
889
+ if (prop.isEventHandler)
890
+ continue;
891
+ if (prop.name.includes("-"))
892
+ continue;
893
+ const resolved = resolvePropValue(prop.value, bindings);
894
+ if (resolved === undefined)
895
+ return null;
896
+ const goValue = scalarToGoLiteral(resolved);
897
+ if (goValue === null)
898
+ return null;
899
+ inputFields.push({ goField: capitalizeFieldName(prop.name), goValue });
900
+ }
901
+ let dataKey = null;
902
+ if (nested.loopKey) {
903
+ const keyExpr = parseExpression(nested.loopKey);
904
+ const keyResolved = evaluateStaticLiteral(keyExpr, bindings);
905
+ if (keyResolved === null)
906
+ return null;
907
+ dataKey = String(keyResolved.value);
908
+ }
909
+ items.push({ inputFields, dataKey });
910
+ }
911
+ return { items };
912
+ }
913
+ function resolvePropValue(value, bindings) {
914
+ switch (value.kind) {
915
+ case "literal":
916
+ return value.value;
917
+ case "boolean-shorthand":
918
+ case "boolean-attr":
919
+ return true;
920
+ case "expression": {
921
+ if (!value.parsed)
922
+ return;
923
+ const resolved = evaluateStaticLiteral(value.parsed, bindings);
924
+ return resolved === null ? undefined : resolved.value;
925
+ }
926
+ default:
927
+ return;
928
+ }
929
+ }
930
+
931
+ // src/adapter/analysis/static-element-loop-bake.ts
932
+ import {
933
+ evaluateStaticLiteral as evaluateStaticLiteral2,
934
+ resolveStaticLoopSource as resolveStaticLoopSource2
935
+ } from "@barefootjs/jsx";
936
+ var ALLOWED_ATTR_EXPRESSION_KINDS = new Set([
937
+ "identifier",
938
+ "member",
939
+ "index-access",
940
+ "literal"
941
+ ]);
942
+ function analyzeBakeableStaticElementLoop(loop, localConstants, opts) {
943
+ if (loop.childComponent)
944
+ return null;
945
+ if (loop.method === "flatMap" || loop.flatMapCallback)
946
+ return null;
947
+ if (!loop.param || /^[{[]/.test(loop.param))
948
+ return null;
949
+ if (loop.index && loop.index !== "_")
950
+ return null;
951
+ if (loop.paramBindings && loop.paramBindings.length > 0)
952
+ return null;
953
+ if (loop.filterPredicate || loop.sortComparator)
954
+ return null;
955
+ if (loop.iterationShape || loop.objectIteration)
956
+ return null;
957
+ if (loop.bodyIsMultiRoot || loop.bodyIsItemConditional)
958
+ return null;
959
+ if (!isFoldableTree(loop.children))
960
+ return null;
961
+ const items = resolveStaticLoopSource2(loop.arrayParsed, localConstants, opts);
962
+ if (items === null)
963
+ return null;
964
+ for (const item of items) {
965
+ const bindings = new Map([[loop.param, item]]);
966
+ if (!allExpressionsFoldFor(loop.children, bindings))
967
+ return null;
968
+ }
969
+ return { items };
970
+ }
971
+ function isFoldableTree(nodes) {
972
+ for (const node of nodes) {
973
+ switch (node.type) {
974
+ case "text":
975
+ case "expression":
976
+ continue;
977
+ case "element":
978
+ if (!isFoldableAttrs(node))
979
+ return false;
980
+ if (!isFoldableTree(node.children))
981
+ return false;
982
+ continue;
983
+ default:
984
+ return false;
985
+ }
986
+ }
987
+ return true;
988
+ }
989
+ function isFoldableAttrs(element) {
990
+ for (const attr of element.attrs) {
991
+ if (attr.clientOnly)
992
+ continue;
993
+ switch (attr.value.kind) {
994
+ case "literal":
995
+ case "boolean-attr":
996
+ case "boolean-shorthand":
997
+ continue;
998
+ case "expression":
999
+ if (!attr.value.parsed || !ALLOWED_ATTR_EXPRESSION_KINDS.has(attr.value.parsed.kind))
1000
+ return false;
1001
+ continue;
1002
+ default:
1003
+ return false;
1004
+ }
1005
+ }
1006
+ return true;
1007
+ }
1008
+ function allExpressionsFoldFor(nodes, bindings) {
1009
+ for (const node of nodes) {
1010
+ if (node.type === "expression") {
1011
+ if (node.clientOnly)
1012
+ continue;
1013
+ if (!node.parsed || !resolvesToScalar(node.parsed, bindings))
1014
+ return false;
1015
+ continue;
1016
+ }
1017
+ if (node.type === "element") {
1018
+ for (const attr of node.attrs) {
1019
+ if (attr.clientOnly)
1020
+ continue;
1021
+ if (attr.value.kind !== "expression")
1022
+ continue;
1023
+ if (!attr.value.parsed || !resolvesToScalar(attr.value.parsed, bindings))
1024
+ return false;
1025
+ }
1026
+ if (!allExpressionsFoldFor(node.children, bindings))
1027
+ return false;
1028
+ }
1029
+ }
1030
+ return true;
1031
+ }
1032
+ function resolvesToScalar(expr, bindings) {
1033
+ const resolved = evaluateStaticLiteral2(expr, bindings);
1034
+ if (resolved === null)
1035
+ return false;
1036
+ return scalarToGoLiteral(resolved.value) !== null;
1037
+ }
1038
+
860
1039
  // src/adapter/expr/helper-inline.ts
861
1040
  function inlineLocalHelperCall(ctx, jsExpr, callParsed) {
862
1041
  if (ctx.state.localHelperNames.size === 0)
@@ -1044,7 +1223,7 @@ function forEachValueChild(n, visit) {
1044
1223
 
1045
1224
  // src/adapter/expr/url-builder.ts
1046
1225
  import {
1047
- parseExpression,
1226
+ parseExpression as parseExpression2,
1048
1227
  stringifyParsedExpr,
1049
1228
  isValidHelperId
1050
1229
  } from "@barefootjs/jsx";
@@ -1077,7 +1256,7 @@ function lowerRegisteredCall(ctx, jsExpr, preParsed) {
1077
1256
  if (!call) {
1078
1257
  if (!/^\s*[A-Za-z_$][\w$]*\s*\(/.test(jsExpr))
1079
1258
  return null;
1080
- const parsed = parseExpression(jsExpr);
1259
+ const parsed = parseExpression2(jsExpr);
1081
1260
  if (parsed.kind !== "call")
1082
1261
  return null;
1083
1262
  call = parsed;
@@ -2171,7 +2350,7 @@ function propsAccessNameFromParsed2(ctx, node) {
2171
2350
 
2172
2351
  // src/adapter/spread/spread-codegen.ts
2173
2352
  import ts2 from "typescript";
2174
- import { parseExpression as parseExpression2, parseRecordIndexAccess } from "@barefootjs/jsx";
2353
+ import { parseExpression as parseExpression3, parseRecordIndexAccess } from "@barefootjs/jsx";
2175
2354
  function collectSpreadSlots(ctx, node) {
2176
2355
  const result = [];
2177
2356
  collectSpreadSlotsRecursive(ctx, node, result);
@@ -2278,7 +2457,7 @@ function parsedObjectLiteralToGoMap(parsed) {
2278
2457
  }
2279
2458
  function buildSpreadInitializer(ctx, spreadExpr, ir, parsed) {
2280
2459
  const trimmed = spreadExpr.trim();
2281
- const conditionalTree = parsed ?? parseExpression2(trimmed);
2460
+ const conditionalTree = parsed ?? parseExpression3(trimmed);
2282
2461
  const conditional = buildConditionalSpreadInitializer(ctx, conditionalTree, ir);
2283
2462
  if (conditional !== undefined)
2284
2463
  return conditional;
@@ -2309,7 +2488,7 @@ function buildSpreadInitializer(ctx, spreadExpr, ir, parsed) {
2309
2488
  if (localConst?.value !== undefined) {
2310
2489
  const initTrimmed = localConst.value.trim();
2311
2490
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(initTrimmed)) {
2312
- const resolved = buildConditionalSpreadInitializer(ctx, parseExpression2(initTrimmed), ir);
2491
+ const resolved = buildConditionalSpreadInitializer(ctx, parseExpression3(initTrimmed), ir);
2313
2492
  if (resolved)
2314
2493
  return resolved;
2315
2494
  if (resolved === null)
@@ -2500,6 +2679,7 @@ function collectNillablePropNames(ctx, ir) {
2500
2679
  }
2501
2680
 
2502
2681
  // src/adapter/props/prop-classes.ts
2682
+ import { collectLoopBoundNames } from "@barefootjs/jsx";
2503
2683
  function isStringTypeInfo(type) {
2504
2684
  return type.kind === "primitive" && type.primitive === "string";
2505
2685
  }
@@ -2520,6 +2700,13 @@ function collectStringValueNames(ir) {
2520
2700
  if (isStringTypeInfo(p.type))
2521
2701
  names.add(p.name);
2522
2702
  }
2703
+ for (const c of ir.metadata.localConstants) {
2704
+ if (c.type !== null && isStringTypeInfo(c.type) || isBareStringLiteral(c.value)) {
2705
+ names.add(c.name);
2706
+ }
2707
+ }
2708
+ for (const bound of collectLoopBoundNames(ir))
2709
+ names.delete(bound);
2523
2710
  return names;
2524
2711
  }
2525
2712
 
@@ -2564,6 +2751,7 @@ class GoTemplateAdapter extends BaseAdapter {
2564
2751
  return this.state.errors;
2565
2752
  }
2566
2753
  inLoop = false;
2754
+ bakedStaticChildLoopCache = new Map;
2567
2755
  loopParamStack = [];
2568
2756
  loopKeyDepthStack = [];
2569
2757
  loopScalarItemStack = [];
@@ -2571,6 +2759,8 @@ class GoTemplateAdapter extends BaseAdapter {
2571
2759
  loopVarRefCount = new Map;
2572
2760
  loopBindingStack = [];
2573
2761
  loopRestExcludeStack = [];
2762
+ staticLoopItemStack = [];
2763
+ staticLoopBakeFailed = false;
2574
2764
  childComponentShapes = new Map;
2575
2765
  childContextConsumers = new Map;
2576
2766
  constructor(options = {}) {
@@ -2586,6 +2776,8 @@ class GoTemplateAdapter extends BaseAdapter {
2586
2776
  this.state.restPropsName = ir.metadata.restPropsName ?? null;
2587
2777
  this.state.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
2588
2778
  this.state.localConstants = ir.metadata.localConstants ?? [];
2779
+ this.state.staticLoopSourceBoundNames = collectLoopBoundNames2(ir);
2780
+ this.bakedStaticChildLoopCache = new Map;
2589
2781
  this.state.localHelperNames = new Set(this.state.localConstants.filter((c) => !c.isModule && c.containsArrow).map((c) => c.name));
2590
2782
  this.state.currentMemos = ir.metadata.memos ?? [];
2591
2783
  this.state.currentTypeDefinitions = ir.metadata.typeDefinitions ?? [];
@@ -2967,6 +3159,8 @@ ${goFields.join(`
2967
3159
  lines.push(` ${fieldName} ${goType}`);
2968
3160
  }
2969
3161
  for (const nested of inputNested) {
3162
+ if (nested.loopMarkerId && this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey))
3163
+ continue;
2970
3164
  lines.push(` ${nested.name}s []${nested.name}Input`);
2971
3165
  }
2972
3166
  const takenInput = new Set(ir.metadata.propsParams.map((p) => capitalizeFieldName(p.name)));
@@ -3098,6 +3292,21 @@ ${goFields.join(`
3098
3292
  const staticWithoutBody = staticNested.filter((n) => !n.bodyChildren || n.bodyChildren.length === 0);
3099
3293
  for (const nested of staticWithoutBody) {
3100
3294
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
3295
+ const baked = nested.loopMarkerId ? this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey) : null;
3296
+ if (baked) {
3297
+ lines.push(` ${varName} := make([]${nested.name}Props, ${baked.items.length})`);
3298
+ baked.items.forEach((item, i) => {
3299
+ const fields = item.inputFields.map((f) => `${f.goField}: ${f.goValue}`).join(", ");
3300
+ lines.push(` ${varName}[${i}] = New${nested.name}Props(${nested.name}Input{${fields}})`);
3301
+ lines.push(` ${varName}[${i}].BfParent = scopeID`);
3302
+ lines.push(` ${varName}[${i}].BfMount = "${nested.slotId}"`);
3303
+ if (item.dataKey !== null) {
3304
+ lines.push(` ${varName}[${i}].BfDataKey = ${JSON.stringify(item.dataKey)}`);
3305
+ }
3306
+ });
3307
+ lines.push("");
3308
+ continue;
3309
+ }
3101
3310
  lines.push(` ${varName} := make([]${nested.name}Props, len(in.${nested.name}s))`);
3102
3311
  lines.push(` for i, item := range in.${nested.name}s {`);
3103
3312
  lines.push(` ${varName}[i] = New${nested.name}Props(item)`);
@@ -4151,7 +4360,8 @@ ${goFields.join(`
4151
4360
  renderElement(element) {
4152
4361
  const tag = element.tag;
4153
4362
  const attrs = this.renderAttributes(element);
4154
- const children = this.renderChildren(element.children);
4363
+ const dangerousHtml = this.renderDangerousInnerHtml(element);
4364
+ const children = dangerousHtml !== null ? dangerousHtml : this.renderChildren(element.children);
4155
4365
  let hydrationAttrs = "";
4156
4366
  if (element.needsScope) {
4157
4367
  hydrationAttrs += ` ${this.renderScopeMarker(".ScopeID")}`;
@@ -4186,6 +4396,22 @@ ${goFields.join(`
4186
4396
  }
4187
4397
  return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
4188
4398
  }
4399
+ renderDangerousInnerHtml(element) {
4400
+ const resolution = resolveDangerousInnerHtml(element);
4401
+ if (!resolution)
4402
+ return null;
4403
+ if (resolution.kind === "dynamic") {
4404
+ this.errors.push(dangerousInnerHtmlDiagnostic(resolution.expr, resolution.loc));
4405
+ return "";
4406
+ }
4407
+ const violation = dangerousInnerHtmlMetacharViolation(resolution.html, this.name);
4408
+ if (violation) {
4409
+ const attr = element.attrs.find(isDangerousInnerHtmlAttr);
4410
+ this.errors.push(dangerousInnerHtmlDiagnostic(`{ __html: ${JSON.stringify(resolution.html)} }`, attr.loc, violation));
4411
+ return "";
4412
+ }
4413
+ return resolution.html;
4414
+ }
4189
4415
  renderExpression(expr) {
4190
4416
  if (expr.clientOnly) {
4191
4417
  if (expr.slotId) {
@@ -4919,8 +5145,8 @@ ${goFields.join(`
4919
5145
  const value = negated ? "false" : "true";
4920
5146
  return `len (bf_filter ${arrayExpr} "${field}" ${value})`;
4921
5147
  }
4922
- renderPredicateCondition(pred, param) {
4923
- return this.renderFilterExpr(pred, param);
5148
+ renderPredicateCondition(pred, param, datumField) {
5149
+ return this.renderFilterExpr(pred, param, new Map, datumField ?? undefined);
4924
5150
  }
4925
5151
  needsParens(expr) {
4926
5152
  return expr.kind === "logical" || expr.kind === "unary" || expr.kind === "conditional";
@@ -4941,21 +5167,23 @@ ${goFields.join(`
4941
5167
  }
4942
5168
  return null;
4943
5169
  }
4944
- renderFilterExpr(expr, param, localVarMap = new Map) {
5170
+ renderFilterExpr(expr, param, localVarMap = new Map, datumField) {
4945
5171
  if (this.filterExprDepth === 0)
4946
5172
  this.filterExprUnsupported = false;
4947
5173
  this.filterExprDepth++;
4948
5174
  try {
4949
- return this.renderFilterExprNode(expr, param, localVarMap);
5175
+ return this.renderFilterExprNode(expr, param, localVarMap, datumField);
4950
5176
  } finally {
4951
5177
  this.filterExprDepth--;
4952
5178
  }
4953
5179
  }
4954
- renderFilterExprNode(expr, param, localVarMap) {
5180
+ renderFilterExprNode(expr, param, localVarMap, datumField) {
5181
+ const paramPrefix = datumField ? `.${datumField}` : "";
5182
+ const paramDot = paramPrefix || ".";
4955
5183
  switch (expr.kind) {
4956
5184
  case "identifier": {
4957
5185
  if (expr.name === param) {
4958
- return ".";
5186
+ return paramDot;
4959
5187
  }
4960
5188
  const signal = localVarMap.get(expr.name);
4961
5189
  if (signal) {
@@ -4973,24 +5201,24 @@ ${goFields.join(`
4973
5201
  return String(expr.value);
4974
5202
  case "member": {
4975
5203
  if (expr.object.kind === "identifier" && expr.object.name === param) {
4976
- return `.${capitalizeFieldName(expr.property)}`;
5204
+ return `${paramPrefix}.${capitalizeFieldName(expr.property)}`;
4977
5205
  }
4978
5206
  if (expr.property === "length") {
4979
5207
  const innerHO = this.higherOrderShapeOf(expr.object);
4980
5208
  if (innerHO && innerHO.method === "filter") {
4981
- const lenExpr = this.renderFilterLengthExpr(innerHO, (e) => this.renderFilterExpr(e, param, localVarMap));
5209
+ const lenExpr = this.renderFilterLengthExpr(innerHO, (e) => this.renderFilterExpr(e, param, localVarMap, datumField));
4982
5210
  if (lenExpr)
4983
5211
  return `(${lenExpr})`;
4984
5212
  }
4985
5213
  }
4986
- const obj = this.renderFilterExpr(expr.object, param, localVarMap);
5214
+ const obj = this.renderFilterExpr(expr.object, param, localVarMap, datumField);
4987
5215
  if (this.filterExprUnsupported)
4988
5216
  return "false";
4989
5217
  return `${obj}.${capitalizeFieldName(expr.property)}`;
4990
5218
  }
4991
5219
  case "call": {
4992
5220
  if (expr.callee.kind === "member" && expr.callee.object.kind === "identifier" && expr.callee.object.name === param) {
4993
- return `.${capitalizeFieldName(expr.callee.property)}`;
5221
+ return `${paramPrefix}.${capitalizeFieldName(expr.callee.property)}`;
4994
5222
  }
4995
5223
  if (expr.callee.kind === "identifier" && expr.args.length === 0) {
4996
5224
  return `$.${capitalizeFieldName(expr.callee.name)}`;
@@ -4998,13 +5226,13 @@ ${goFields.join(`
4998
5226
  if (asCallbackMethodCall3(expr) !== null) {
4999
5227
  return this.refuseFilterExprNode(expr);
5000
5228
  }
5001
- const result = this.renderFilterExpr(expr.callee, param, localVarMap);
5229
+ const result = this.renderFilterExpr(expr.callee, param, localVarMap, datumField);
5002
5230
  if (this.filterExprUnsupported)
5003
5231
  return "false";
5004
5232
  return result;
5005
5233
  }
5006
5234
  case "unary": {
5007
- const arg = this.renderFilterExpr(expr.argument, param, localVarMap);
5235
+ const arg = this.renderFilterExpr(expr.argument, param, localVarMap, datumField);
5008
5236
  if (this.filterExprUnsupported)
5009
5237
  return "false";
5010
5238
  if (expr.op === "!") {
@@ -5017,10 +5245,10 @@ ${goFields.join(`
5017
5245
  return arg;
5018
5246
  }
5019
5247
  case "binary": {
5020
- const left = this.renderFilterExpr(expr.left, param, localVarMap);
5248
+ const left = this.renderFilterExpr(expr.left, param, localVarMap, datumField);
5021
5249
  if (this.filterExprUnsupported)
5022
5250
  return "false";
5023
- const right = this.renderFilterExpr(expr.right, param, localVarMap);
5251
+ const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField);
5024
5252
  if (this.filterExprUnsupported)
5025
5253
  return "false";
5026
5254
  switch (expr.op) {
@@ -5051,10 +5279,10 @@ ${goFields.join(`
5051
5279
  }
5052
5280
  }
5053
5281
  case "logical": {
5054
- const left = this.renderFilterExpr(expr.left, param, localVarMap);
5282
+ const left = this.renderFilterExpr(expr.left, param, localVarMap, datumField);
5055
5283
  if (this.filterExprUnsupported)
5056
5284
  return "false";
5057
- const right = this.renderFilterExpr(expr.right, param, localVarMap);
5285
+ const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField);
5058
5286
  if (this.filterExprUnsupported)
5059
5287
  return "false";
5060
5288
  if (expr.op === "&&") {
@@ -5122,11 +5350,22 @@ ${goFields.join(`
5122
5350
  if (trimmed === "null" || trimmed === "undefined") {
5123
5351
  return '""';
5124
5352
  }
5353
+ if (this.staticLoopItemStack.length > 0) {
5354
+ const top = this.staticLoopItemStack[this.staticLoopItemStack.length - 1];
5355
+ const parsedForBake = preParsed ?? parseExpression4(trimmed);
5356
+ const resolved = evaluateStaticLiteral3(parsedForBake, new Map([[top.param, top.item]]));
5357
+ const literal = resolved !== null ? scalarToGoLiteral(resolved.value) : null;
5358
+ if (literal !== null) {
5359
+ return literal;
5360
+ }
5361
+ this.staticLoopBakeFailed = true;
5362
+ return '""';
5363
+ }
5125
5364
  const staticIndexed = this.resolveStaticRecordLiteralIndex(trimmed);
5126
5365
  if (staticIndexed !== null) {
5127
5366
  return staticIndexed;
5128
5367
  }
5129
- if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
5368
+ if (!this.isLoopShadowedName(trimmed) && /^[A-Za-z_$][\w$]*$/.test(trimmed)) {
5130
5369
  const litConst = (this.state.localConstants ?? []).find((c) => c.name === trimmed);
5131
5370
  if (litConst?.value !== undefined) {
5132
5371
  const v = litConst.value.trim();
@@ -5144,7 +5383,7 @@ ${goFields.join(`
5144
5383
  if (inlined !== null) {
5145
5384
  return this.convertExpressionToGo(stringifyParsedExpr2(inlined), out, inlined);
5146
5385
  }
5147
- const parsed = preParsed ?? parseExpression3(trimmed);
5386
+ const parsed = preParsed ?? parseExpression4(trimmed);
5148
5387
  const support = isSupported(parsed);
5149
5388
  if (!support.supported) {
5150
5389
  this.state.errors.push({
@@ -5162,10 +5401,15 @@ ${goFields.join(`
5162
5401
  out.parsed = parsed;
5163
5402
  return this.renderParsedExpr(parsed);
5164
5403
  }
5404
+ isLoopShadowedName(name) {
5405
+ 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));
5406
+ }
5165
5407
  resolveStaticRecordLiteralIndex(jsExpr) {
5166
5408
  const m = /^([A-Za-z_$][\w$]*)\[\s*(?:'([^']*)'|"([^"]*)")\s*\]$/.exec(jsExpr) ?? /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/.exec(jsExpr);
5167
5409
  if (!m)
5168
5410
  return null;
5411
+ if (this.isLoopShadowedName(m[1]))
5412
+ return null;
5169
5413
  const key = m[2] ?? m[3];
5170
5414
  const constInfo = (this.state.localConstants ?? []).find((c) => c.name === m[1] && c.isModule);
5171
5415
  if (constInfo?.value === undefined)
@@ -5271,7 +5515,7 @@ ${goFields.join(`
5271
5515
  }
5272
5516
  convertConditionToGo(jsCondition, preParsed) {
5273
5517
  const trimmed = jsCondition.trim();
5274
- const parsed = preParsed ?? parseExpression3(trimmed);
5518
+ const parsed = preParsed ?? parseExpression4(trimmed);
5275
5519
  const support = isSupported(parsed);
5276
5520
  if (!support.supported) {
5277
5521
  this.state.errors.push({
@@ -5492,6 +5736,29 @@ ${goFields.join(`
5492
5736
  }
5493
5737
  return;
5494
5738
  }
5739
+ wrapperDatumField(loop) {
5740
+ if (!loop.childComponent)
5741
+ return null;
5742
+ for (const prop of loop.childComponent.props) {
5743
+ if (prop.isEventHandler)
5744
+ continue;
5745
+ if (prop.value.kind !== "expression")
5746
+ continue;
5747
+ const parsed = prop.value.parsed;
5748
+ const isBareParamRef = parsed ? parsed.kind === "identifier" && parsed.name === loop.param : prop.value.expr.trim() === loop.param;
5749
+ if (isBareParamRef)
5750
+ return capitalizeFieldName(prop.name);
5751
+ }
5752
+ return null;
5753
+ }
5754
+ getBakedStaticChildLoop(markerId, childComponent, arrayParsed, param, key) {
5755
+ if (this.bakedStaticChildLoopCache.has(markerId)) {
5756
+ return this.bakedStaticChildLoopCache.get(markerId) ?? null;
5757
+ }
5758
+ const result = analyzeBakeableStaticChildLoop({ props: childComponent.props, loopArrayParsed: arrayParsed, loopParam: param, loopKey: key }, this.state.localConstants, { isNameShadowed: (name) => this.state.staticLoopSourceBoundNames.has(name) });
5759
+ this.bakedStaticChildLoopCache.set(markerId, result);
5760
+ return result;
5761
+ }
5495
5762
  renderLoop(loop) {
5496
5763
  if (loop.clientOnly) {
5497
5764
  return `{{bfComment "loop:${loop.markerId}"}}{{bfComment "/loop:${loop.markerId}"}}`;
@@ -5512,8 +5779,13 @@ ${goFields.join(`
5512
5779
  }
5513
5780
  });
5514
5781
  }
5782
+ const bakedChildLoop = loop.childComponent ? this.getBakedStaticChildLoop(loop.markerId, loop.childComponent, loop.arrayParsed, loop.param, loop.key ?? undefined) : null;
5783
+ const bakedElementLoop = loop.childComponent ? null : analyzeBakeableStaticElementLoop(loop, this.state.localConstants, { isNameShadowed: (name) => this.state.staticLoopSourceBoundNames.has(name) });
5784
+ if (bakedElementLoop) {
5785
+ return this.renderUnrolledStaticElementLoop(loop, bakedElementLoop.items);
5786
+ }
5515
5787
  const arrayName = loop.array.trim();
5516
- if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
5788
+ if (bakedChildLoop === null && /^[A-Za-z_$][\w$]*$/.test(arrayName)) {
5517
5789
  const arrayConst = this.state.localConstants.find((c) => c.name === arrayName);
5518
5790
  if (arrayConst && !arrayConst.isModule && arrayConst.parsed && !this.isStringExpr(arrayConst.parsed, new Set)) {
5519
5791
  this.state.errors.push({
@@ -5527,7 +5799,7 @@ ${goFields.join(`
5527
5799
  });
5528
5800
  }
5529
5801
  }
5530
- let goArray = this.convertExpressionToGo(loop.array);
5802
+ let goArray = loop.childComponent ? "" : this.convertExpressionToGo(loop.array);
5531
5803
  const param = loop.param;
5532
5804
  let index = loop.index || "_";
5533
5805
  let rangeIndex = index;
@@ -5595,7 +5867,8 @@ ${goFields.join(`
5595
5867
  if (loop.filterPredicate) {
5596
5868
  let filterCond;
5597
5869
  if (loop.filterPredicate.predicate) {
5598
- filterCond = this.renderPredicateCondition(loop.filterPredicate.predicate, loop.filterPredicate.param);
5870
+ const datumField = this.wrapperDatumField(loop);
5871
+ filterCond = this.renderPredicateCondition(loop.filterPredicate.predicate, loop.filterPredicate.param, datumField);
5599
5872
  } else {
5600
5873
  filterCond = "true";
5601
5874
  }
@@ -5603,6 +5876,38 @@ ${goFields.join(`
5603
5876
  }
5604
5877
  return `{{bfComment "loop:${loop.markerId}"}}{{range $${rangeIndex}, $${rangeValue} := ${goArray}}}${itemMarker}${children}{{end}}{{bfComment "/loop:${loop.markerId}"}}`;
5605
5878
  }
5879
+ renderUnrolledStaticElementLoop(loop, items) {
5880
+ this.inLoop = true;
5881
+ this.loopWrapperStack.push(false);
5882
+ this.loopKeyDepthStack.push(loop.depth);
5883
+ this.loopScalarItemStack.push(this.scalarLiteralLoopGoType(loop.arrayParsed, loop.itemType) !== null);
5884
+ this.loopParamStack.push(loop.param);
5885
+ let body = "";
5886
+ for (const item of items) {
5887
+ this.staticLoopItemStack.push({ param: loop.param, item });
5888
+ body += this.renderChildren(loop.children);
5889
+ this.staticLoopItemStack.pop();
5890
+ if (this.staticLoopBakeFailed) {
5891
+ this.staticLoopBakeFailed = false;
5892
+ this.state.errors.push({
5893
+ code: "BF101",
5894
+ severity: "error",
5895
+ 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.`,
5896
+ loc: loop.loc ?? this.makeLoc(),
5897
+ suggestion: {
5898
+ 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."
5899
+ }
5900
+ });
5901
+ break;
5902
+ }
5903
+ }
5904
+ this.loopParamStack.pop();
5905
+ this.loopScalarItemStack.pop();
5906
+ this.loopKeyDepthStack.pop();
5907
+ this.loopWrapperStack.pop();
5908
+ this.inLoop = false;
5909
+ return `{{bfComment "loop:${loop.markerId}"}}${body}{{bfComment "/loop:${loop.markerId}"}}`;
5910
+ }
5606
5911
  loopItemMarker(loop) {
5607
5912
  if (loop.bodyIsMultiRoot)
5608
5913
  return `{{bfComment "bf-loop-i"}}`;
@@ -5722,7 +6027,7 @@ ${children}`;
5722
6027
  const body = name.startsWith("aria-") ? `${name}="true"` : name;
5723
6028
  return `${preamble}{{if ${goCond}}}${body}{{end}}`;
5724
6029
  }
5725
- const parsed = value.parsed ?? parseExpression3(value.expr.trim());
6030
+ const parsed = value.parsed ?? parseExpression4(value.expr.trim());
5726
6031
  if (parsed.kind === "conditional") {
5727
6032
  const undef = (e) => e.kind === "identifier" && (e.name === "undefined" || e.name === "null") || e.kind === "literal" && (e.value === null || e.value === undefined);
5728
6033
  const test = parsed.test;
@@ -5787,7 +6092,7 @@ ${children}`;
5787
6092
  if (!entries)
5788
6093
  return null;
5789
6094
  for (const e of entries) {
5790
- if (e.kind === "expr" && !isSupported(parseExpression3(e.expr)).supported)
6095
+ if (e.kind === "expr" && !isSupported(parseExpression4(e.expr)).supported)
5791
6096
  return null;
5792
6097
  }
5793
6098
  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 +6102,8 @@ ${children}`;
5797
6102
  for (const attr of element.attrs) {
5798
6103
  if (attr.clientOnly)
5799
6104
  continue;
6105
+ if (isDangerousInnerHtmlAttr(attr))
6106
+ continue;
5800
6107
  let attrName;
5801
6108
  if (attr.name === "className")
5802
6109
  attrName = "class";
@@ -1 +1 @@
1
- {"version":3,"file":"conformance-pins.d.ts","sourceRoot":"","sources":["../src/conformance-pins.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEtD,eAAO,MAAM,eAAe,EAAE,eAgI7B,CAAA"}
1
+ {"version":3,"file":"conformance-pins.d.ts","sourceRoot":"","sources":["../src/conformance-pins.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEtD,eAAO,MAAM,eAAe,EAAE,eA+H7B,CAAA"}