@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.
@@ -25,7 +25,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
25
25
  import {
26
26
  BaseAdapter,
27
27
  isBooleanAttr,
28
- parseExpression as parseExpression3,
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 = [];
@@ -517,6 +524,178 @@ function collectNestedComponents(node, result) {
517
524
  }
518
525
  }
519
526
 
527
+ // src/adapter/analysis/static-child-loop-bake.ts
528
+ import { evaluateStaticLiteral, parseExpression, resolveStaticLoopSource } from "@barefootjs/jsx";
529
+ function scalarToGoLiteral(value) {
530
+ if (typeof value === "string")
531
+ return `"${escapeGoString(value)}"`;
532
+ if (typeof value === "number")
533
+ return String(value);
534
+ if (typeof value === "boolean")
535
+ return value ? "true" : "false";
536
+ return null;
537
+ }
538
+ function analyzeBakeableStaticChildLoop(nested, localConstants, opts) {
539
+ if (!nested.loopParam || /^[{[]/.test(nested.loopParam))
540
+ return null;
541
+ const staticItemsResult = resolveStaticLoopSource(nested.loopArrayParsed, localConstants, opts);
542
+ if (staticItemsResult === null)
543
+ return null;
544
+ const items = [];
545
+ for (const item of staticItemsResult) {
546
+ const bindings = new Map([[nested.loopParam, item]]);
547
+ const inputFields = [];
548
+ for (const prop of nested.props) {
549
+ if (prop.isEventHandler)
550
+ continue;
551
+ if (prop.name.includes("-"))
552
+ continue;
553
+ const resolved = resolvePropValue(prop.value, bindings);
554
+ if (resolved === undefined)
555
+ return null;
556
+ const goValue = scalarToGoLiteral(resolved);
557
+ if (goValue === null)
558
+ return null;
559
+ inputFields.push({ goField: capitalizeFieldName(prop.name), goValue });
560
+ }
561
+ let dataKey = null;
562
+ if (nested.loopKey) {
563
+ const keyExpr = parseExpression(nested.loopKey);
564
+ const keyResolved = evaluateStaticLiteral(keyExpr, bindings);
565
+ if (keyResolved === null)
566
+ return null;
567
+ dataKey = String(keyResolved.value);
568
+ }
569
+ items.push({ inputFields, dataKey });
570
+ }
571
+ return { items };
572
+ }
573
+ function resolvePropValue(value, bindings) {
574
+ switch (value.kind) {
575
+ case "literal":
576
+ return value.value;
577
+ case "boolean-shorthand":
578
+ case "boolean-attr":
579
+ return true;
580
+ case "expression": {
581
+ if (!value.parsed)
582
+ return;
583
+ const resolved = evaluateStaticLiteral(value.parsed, bindings);
584
+ return resolved === null ? undefined : resolved.value;
585
+ }
586
+ default:
587
+ return;
588
+ }
589
+ }
590
+
591
+ // src/adapter/analysis/static-element-loop-bake.ts
592
+ import {
593
+ evaluateStaticLiteral as evaluateStaticLiteral2,
594
+ resolveStaticLoopSource as resolveStaticLoopSource2
595
+ } from "@barefootjs/jsx";
596
+ var ALLOWED_ATTR_EXPRESSION_KINDS = new Set([
597
+ "identifier",
598
+ "member",
599
+ "index-access",
600
+ "literal"
601
+ ]);
602
+ function analyzeBakeableStaticElementLoop(loop, localConstants, opts) {
603
+ if (loop.childComponent)
604
+ return null;
605
+ if (loop.method === "flatMap" || loop.flatMapCallback)
606
+ return null;
607
+ if (!loop.param || /^[{[]/.test(loop.param))
608
+ return null;
609
+ if (loop.index && loop.index !== "_")
610
+ return null;
611
+ if (loop.paramBindings && loop.paramBindings.length > 0)
612
+ return null;
613
+ if (loop.filterPredicate || loop.sortComparator)
614
+ return null;
615
+ if (loop.iterationShape || loop.objectIteration)
616
+ return null;
617
+ if (loop.bodyIsMultiRoot || loop.bodyIsItemConditional)
618
+ return null;
619
+ if (!isFoldableTree(loop.children))
620
+ return null;
621
+ const items = resolveStaticLoopSource2(loop.arrayParsed, localConstants, opts);
622
+ if (items === null)
623
+ return null;
624
+ for (const item of items) {
625
+ const bindings = new Map([[loop.param, item]]);
626
+ if (!allExpressionsFoldFor(loop.children, bindings))
627
+ return null;
628
+ }
629
+ return { items };
630
+ }
631
+ function isFoldableTree(nodes) {
632
+ for (const node of nodes) {
633
+ switch (node.type) {
634
+ case "text":
635
+ case "expression":
636
+ continue;
637
+ case "element":
638
+ if (!isFoldableAttrs(node))
639
+ return false;
640
+ if (!isFoldableTree(node.children))
641
+ return false;
642
+ continue;
643
+ default:
644
+ return false;
645
+ }
646
+ }
647
+ return true;
648
+ }
649
+ function isFoldableAttrs(element) {
650
+ for (const attr of element.attrs) {
651
+ if (attr.clientOnly)
652
+ continue;
653
+ switch (attr.value.kind) {
654
+ case "literal":
655
+ case "boolean-attr":
656
+ case "boolean-shorthand":
657
+ continue;
658
+ case "expression":
659
+ if (!attr.value.parsed || !ALLOWED_ATTR_EXPRESSION_KINDS.has(attr.value.parsed.kind))
660
+ return false;
661
+ continue;
662
+ default:
663
+ return false;
664
+ }
665
+ }
666
+ return true;
667
+ }
668
+ function allExpressionsFoldFor(nodes, bindings) {
669
+ for (const node of nodes) {
670
+ if (node.type === "expression") {
671
+ if (node.clientOnly)
672
+ continue;
673
+ if (!node.parsed || !resolvesToScalar(node.parsed, bindings))
674
+ return false;
675
+ continue;
676
+ }
677
+ if (node.type === "element") {
678
+ for (const attr of node.attrs) {
679
+ if (attr.clientOnly)
680
+ continue;
681
+ if (attr.value.kind !== "expression")
682
+ continue;
683
+ if (!attr.value.parsed || !resolvesToScalar(attr.value.parsed, bindings))
684
+ return false;
685
+ }
686
+ if (!allExpressionsFoldFor(node.children, bindings))
687
+ return false;
688
+ }
689
+ }
690
+ return true;
691
+ }
692
+ function resolvesToScalar(expr, bindings) {
693
+ const resolved = evaluateStaticLiteral2(expr, bindings);
694
+ if (resolved === null)
695
+ return false;
696
+ return scalarToGoLiteral(resolved.value) !== null;
697
+ }
698
+
520
699
  // src/adapter/expr/helper-inline.ts
521
700
  function inlineLocalHelperCall(ctx, jsExpr, callParsed) {
522
701
  if (ctx.state.localHelperNames.size === 0)
@@ -704,7 +883,7 @@ function forEachValueChild(n, visit) {
704
883
 
705
884
  // src/adapter/expr/url-builder.ts
706
885
  import {
707
- parseExpression,
886
+ parseExpression as parseExpression2,
708
887
  stringifyParsedExpr,
709
888
  isValidHelperId
710
889
  } from "@barefootjs/jsx";
@@ -737,7 +916,7 @@ function lowerRegisteredCall(ctx, jsExpr, preParsed) {
737
916
  if (!call) {
738
917
  if (!/^\s*[A-Za-z_$][\w$]*\s*\(/.test(jsExpr))
739
918
  return null;
740
- const parsed = parseExpression(jsExpr);
919
+ const parsed = parseExpression2(jsExpr);
741
920
  if (parsed.kind !== "call")
742
921
  return null;
743
922
  call = parsed;
@@ -1831,7 +2010,7 @@ function propsAccessNameFromParsed2(ctx, node) {
1831
2010
 
1832
2011
  // src/adapter/spread/spread-codegen.ts
1833
2012
  import ts2 from "typescript";
1834
- import { parseExpression as parseExpression2, parseRecordIndexAccess } from "@barefootjs/jsx";
2013
+ import { parseExpression as parseExpression3, parseRecordIndexAccess } from "@barefootjs/jsx";
1835
2014
  function collectSpreadSlots(ctx, node) {
1836
2015
  const result = [];
1837
2016
  collectSpreadSlotsRecursive(ctx, node, result);
@@ -1938,7 +2117,7 @@ function parsedObjectLiteralToGoMap(parsed) {
1938
2117
  }
1939
2118
  function buildSpreadInitializer(ctx, spreadExpr, ir, parsed) {
1940
2119
  const trimmed = spreadExpr.trim();
1941
- const conditionalTree = parsed ?? parseExpression2(trimmed);
2120
+ const conditionalTree = parsed ?? parseExpression3(trimmed);
1942
2121
  const conditional = buildConditionalSpreadInitializer(ctx, conditionalTree, ir);
1943
2122
  if (conditional !== undefined)
1944
2123
  return conditional;
@@ -1969,7 +2148,7 @@ function buildSpreadInitializer(ctx, spreadExpr, ir, parsed) {
1969
2148
  if (localConst?.value !== undefined) {
1970
2149
  const initTrimmed = localConst.value.trim();
1971
2150
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(initTrimmed)) {
1972
- const resolved = buildConditionalSpreadInitializer(ctx, parseExpression2(initTrimmed), ir);
2151
+ const resolved = buildConditionalSpreadInitializer(ctx, parseExpression3(initTrimmed), ir);
1973
2152
  if (resolved)
1974
2153
  return resolved;
1975
2154
  if (resolved === null)
@@ -2160,6 +2339,7 @@ function collectNillablePropNames(ctx, ir) {
2160
2339
  }
2161
2340
 
2162
2341
  // src/adapter/props/prop-classes.ts
2342
+ import { collectLoopBoundNames } from "@barefootjs/jsx";
2163
2343
  function isStringTypeInfo(type) {
2164
2344
  return type.kind === "primitive" && type.primitive === "string";
2165
2345
  }
@@ -2180,6 +2360,13 @@ function collectStringValueNames(ir) {
2180
2360
  if (isStringTypeInfo(p.type))
2181
2361
  names.add(p.name);
2182
2362
  }
2363
+ for (const c of ir.metadata.localConstants) {
2364
+ if (c.type !== null && isStringTypeInfo(c.type) || isBareStringLiteral(c.value)) {
2365
+ names.add(c.name);
2366
+ }
2367
+ }
2368
+ for (const bound of collectLoopBoundNames(ir))
2369
+ names.delete(bound);
2183
2370
  return names;
2184
2371
  }
2185
2372
 
@@ -2224,6 +2411,7 @@ class GoTemplateAdapter extends BaseAdapter {
2224
2411
  return this.state.errors;
2225
2412
  }
2226
2413
  inLoop = false;
2414
+ bakedStaticChildLoopCache = new Map;
2227
2415
  loopParamStack = [];
2228
2416
  loopKeyDepthStack = [];
2229
2417
  loopScalarItemStack = [];
@@ -2231,6 +2419,8 @@ class GoTemplateAdapter extends BaseAdapter {
2231
2419
  loopVarRefCount = new Map;
2232
2420
  loopBindingStack = [];
2233
2421
  loopRestExcludeStack = [];
2422
+ staticLoopItemStack = [];
2423
+ staticLoopBakeFailed = false;
2234
2424
  childComponentShapes = new Map;
2235
2425
  childContextConsumers = new Map;
2236
2426
  constructor(options = {}) {
@@ -2246,6 +2436,8 @@ class GoTemplateAdapter extends BaseAdapter {
2246
2436
  this.state.restPropsName = ir.metadata.restPropsName ?? null;
2247
2437
  this.state.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
2248
2438
  this.state.localConstants = ir.metadata.localConstants ?? [];
2439
+ this.state.staticLoopSourceBoundNames = collectLoopBoundNames2(ir);
2440
+ this.bakedStaticChildLoopCache = new Map;
2249
2441
  this.state.localHelperNames = new Set(this.state.localConstants.filter((c) => !c.isModule && c.containsArrow).map((c) => c.name));
2250
2442
  this.state.currentMemos = ir.metadata.memos ?? [];
2251
2443
  this.state.currentTypeDefinitions = ir.metadata.typeDefinitions ?? [];
@@ -2627,6 +2819,8 @@ ${goFields.join(`
2627
2819
  lines.push(` ${fieldName} ${goType}`);
2628
2820
  }
2629
2821
  for (const nested of inputNested) {
2822
+ if (nested.loopMarkerId && this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey))
2823
+ continue;
2630
2824
  lines.push(` ${nested.name}s []${nested.name}Input`);
2631
2825
  }
2632
2826
  const takenInput = new Set(ir.metadata.propsParams.map((p) => capitalizeFieldName(p.name)));
@@ -2758,6 +2952,21 @@ ${goFields.join(`
2758
2952
  const staticWithoutBody = staticNested.filter((n) => !n.bodyChildren || n.bodyChildren.length === 0);
2759
2953
  for (const nested of staticWithoutBody) {
2760
2954
  const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
2955
+ const baked = nested.loopMarkerId ? this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey) : null;
2956
+ if (baked) {
2957
+ lines.push(` ${varName} := make([]${nested.name}Props, ${baked.items.length})`);
2958
+ baked.items.forEach((item, i) => {
2959
+ const fields = item.inputFields.map((f) => `${f.goField}: ${f.goValue}`).join(", ");
2960
+ lines.push(` ${varName}[${i}] = New${nested.name}Props(${nested.name}Input{${fields}})`);
2961
+ lines.push(` ${varName}[${i}].BfParent = scopeID`);
2962
+ lines.push(` ${varName}[${i}].BfMount = "${nested.slotId}"`);
2963
+ if (item.dataKey !== null) {
2964
+ lines.push(` ${varName}[${i}].BfDataKey = ${JSON.stringify(item.dataKey)}`);
2965
+ }
2966
+ });
2967
+ lines.push("");
2968
+ continue;
2969
+ }
2761
2970
  lines.push(` ${varName} := make([]${nested.name}Props, len(in.${nested.name}s))`);
2762
2971
  lines.push(` for i, item := range in.${nested.name}s {`);
2763
2972
  lines.push(` ${varName}[i] = New${nested.name}Props(item)`);
@@ -3811,7 +4020,8 @@ ${goFields.join(`
3811
4020
  renderElement(element) {
3812
4021
  const tag = element.tag;
3813
4022
  const attrs = this.renderAttributes(element);
3814
- const children = this.renderChildren(element.children);
4023
+ const dangerousHtml = this.renderDangerousInnerHtml(element);
4024
+ const children = dangerousHtml !== null ? dangerousHtml : this.renderChildren(element.children);
3815
4025
  let hydrationAttrs = "";
3816
4026
  if (element.needsScope) {
3817
4027
  hydrationAttrs += ` ${this.renderScopeMarker(".ScopeID")}`;
@@ -3846,6 +4056,22 @@ ${goFields.join(`
3846
4056
  }
3847
4057
  return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
3848
4058
  }
4059
+ renderDangerousInnerHtml(element) {
4060
+ const resolution = resolveDangerousInnerHtml(element);
4061
+ if (!resolution)
4062
+ return null;
4063
+ if (resolution.kind === "dynamic") {
4064
+ this.errors.push(dangerousInnerHtmlDiagnostic(resolution.expr, resolution.loc));
4065
+ return "";
4066
+ }
4067
+ const violation = dangerousInnerHtmlMetacharViolation(resolution.html, this.name);
4068
+ if (violation) {
4069
+ const attr = element.attrs.find(isDangerousInnerHtmlAttr);
4070
+ this.errors.push(dangerousInnerHtmlDiagnostic(`{ __html: ${JSON.stringify(resolution.html)} }`, attr.loc, violation));
4071
+ return "";
4072
+ }
4073
+ return resolution.html;
4074
+ }
3849
4075
  renderExpression(expr) {
3850
4076
  if (expr.clientOnly) {
3851
4077
  if (expr.slotId) {
@@ -4579,8 +4805,8 @@ ${goFields.join(`
4579
4805
  const value = negated ? "false" : "true";
4580
4806
  return `len (bf_filter ${arrayExpr} "${field}" ${value})`;
4581
4807
  }
4582
- renderPredicateCondition(pred, param) {
4583
- return this.renderFilterExpr(pred, param);
4808
+ renderPredicateCondition(pred, param, datumField) {
4809
+ return this.renderFilterExpr(pred, param, new Map, datumField ?? undefined);
4584
4810
  }
4585
4811
  needsParens(expr) {
4586
4812
  return expr.kind === "logical" || expr.kind === "unary" || expr.kind === "conditional";
@@ -4601,21 +4827,23 @@ ${goFields.join(`
4601
4827
  }
4602
4828
  return null;
4603
4829
  }
4604
- renderFilterExpr(expr, param, localVarMap = new Map) {
4830
+ renderFilterExpr(expr, param, localVarMap = new Map, datumField) {
4605
4831
  if (this.filterExprDepth === 0)
4606
4832
  this.filterExprUnsupported = false;
4607
4833
  this.filterExprDepth++;
4608
4834
  try {
4609
- return this.renderFilterExprNode(expr, param, localVarMap);
4835
+ return this.renderFilterExprNode(expr, param, localVarMap, datumField);
4610
4836
  } finally {
4611
4837
  this.filterExprDepth--;
4612
4838
  }
4613
4839
  }
4614
- renderFilterExprNode(expr, param, localVarMap) {
4840
+ renderFilterExprNode(expr, param, localVarMap, datumField) {
4841
+ const paramPrefix = datumField ? `.${datumField}` : "";
4842
+ const paramDot = paramPrefix || ".";
4615
4843
  switch (expr.kind) {
4616
4844
  case "identifier": {
4617
4845
  if (expr.name === param) {
4618
- return ".";
4846
+ return paramDot;
4619
4847
  }
4620
4848
  const signal = localVarMap.get(expr.name);
4621
4849
  if (signal) {
@@ -4633,24 +4861,24 @@ ${goFields.join(`
4633
4861
  return String(expr.value);
4634
4862
  case "member": {
4635
4863
  if (expr.object.kind === "identifier" && expr.object.name === param) {
4636
- return `.${capitalizeFieldName(expr.property)}`;
4864
+ return `${paramPrefix}.${capitalizeFieldName(expr.property)}`;
4637
4865
  }
4638
4866
  if (expr.property === "length") {
4639
4867
  const innerHO = this.higherOrderShapeOf(expr.object);
4640
4868
  if (innerHO && innerHO.method === "filter") {
4641
- const lenExpr = this.renderFilterLengthExpr(innerHO, (e) => this.renderFilterExpr(e, param, localVarMap));
4869
+ const lenExpr = this.renderFilterLengthExpr(innerHO, (e) => this.renderFilterExpr(e, param, localVarMap, datumField));
4642
4870
  if (lenExpr)
4643
4871
  return `(${lenExpr})`;
4644
4872
  }
4645
4873
  }
4646
- const obj = this.renderFilterExpr(expr.object, param, localVarMap);
4874
+ const obj = this.renderFilterExpr(expr.object, param, localVarMap, datumField);
4647
4875
  if (this.filterExprUnsupported)
4648
4876
  return "false";
4649
4877
  return `${obj}.${capitalizeFieldName(expr.property)}`;
4650
4878
  }
4651
4879
  case "call": {
4652
4880
  if (expr.callee.kind === "member" && expr.callee.object.kind === "identifier" && expr.callee.object.name === param) {
4653
- return `.${capitalizeFieldName(expr.callee.property)}`;
4881
+ return `${paramPrefix}.${capitalizeFieldName(expr.callee.property)}`;
4654
4882
  }
4655
4883
  if (expr.callee.kind === "identifier" && expr.args.length === 0) {
4656
4884
  return `$.${capitalizeFieldName(expr.callee.name)}`;
@@ -4658,13 +4886,13 @@ ${goFields.join(`
4658
4886
  if (asCallbackMethodCall3(expr) !== null) {
4659
4887
  return this.refuseFilterExprNode(expr);
4660
4888
  }
4661
- const result = this.renderFilterExpr(expr.callee, param, localVarMap);
4889
+ const result = this.renderFilterExpr(expr.callee, param, localVarMap, datumField);
4662
4890
  if (this.filterExprUnsupported)
4663
4891
  return "false";
4664
4892
  return result;
4665
4893
  }
4666
4894
  case "unary": {
4667
- const arg = this.renderFilterExpr(expr.argument, param, localVarMap);
4895
+ const arg = this.renderFilterExpr(expr.argument, param, localVarMap, datumField);
4668
4896
  if (this.filterExprUnsupported)
4669
4897
  return "false";
4670
4898
  if (expr.op === "!") {
@@ -4677,10 +4905,10 @@ ${goFields.join(`
4677
4905
  return arg;
4678
4906
  }
4679
4907
  case "binary": {
4680
- const left = this.renderFilterExpr(expr.left, param, localVarMap);
4908
+ const left = this.renderFilterExpr(expr.left, param, localVarMap, datumField);
4681
4909
  if (this.filterExprUnsupported)
4682
4910
  return "false";
4683
- const right = this.renderFilterExpr(expr.right, param, localVarMap);
4911
+ const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField);
4684
4912
  if (this.filterExprUnsupported)
4685
4913
  return "false";
4686
4914
  switch (expr.op) {
@@ -4711,10 +4939,10 @@ ${goFields.join(`
4711
4939
  }
4712
4940
  }
4713
4941
  case "logical": {
4714
- const left = this.renderFilterExpr(expr.left, param, localVarMap);
4942
+ const left = this.renderFilterExpr(expr.left, param, localVarMap, datumField);
4715
4943
  if (this.filterExprUnsupported)
4716
4944
  return "false";
4717
- const right = this.renderFilterExpr(expr.right, param, localVarMap);
4945
+ const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField);
4718
4946
  if (this.filterExprUnsupported)
4719
4947
  return "false";
4720
4948
  if (expr.op === "&&") {
@@ -4782,11 +5010,22 @@ ${goFields.join(`
4782
5010
  if (trimmed === "null" || trimmed === "undefined") {
4783
5011
  return '""';
4784
5012
  }
5013
+ if (this.staticLoopItemStack.length > 0) {
5014
+ const top = this.staticLoopItemStack[this.staticLoopItemStack.length - 1];
5015
+ const parsedForBake = preParsed ?? parseExpression4(trimmed);
5016
+ const resolved = evaluateStaticLiteral3(parsedForBake, new Map([[top.param, top.item]]));
5017
+ const literal = resolved !== null ? scalarToGoLiteral(resolved.value) : null;
5018
+ if (literal !== null) {
5019
+ return literal;
5020
+ }
5021
+ this.staticLoopBakeFailed = true;
5022
+ return '""';
5023
+ }
4785
5024
  const staticIndexed = this.resolveStaticRecordLiteralIndex(trimmed);
4786
5025
  if (staticIndexed !== null) {
4787
5026
  return staticIndexed;
4788
5027
  }
4789
- if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
5028
+ if (!this.isLoopShadowedName(trimmed) && /^[A-Za-z_$][\w$]*$/.test(trimmed)) {
4790
5029
  const litConst = (this.state.localConstants ?? []).find((c) => c.name === trimmed);
4791
5030
  if (litConst?.value !== undefined) {
4792
5031
  const v = litConst.value.trim();
@@ -4804,7 +5043,7 @@ ${goFields.join(`
4804
5043
  if (inlined !== null) {
4805
5044
  return this.convertExpressionToGo(stringifyParsedExpr2(inlined), out, inlined);
4806
5045
  }
4807
- const parsed = preParsed ?? parseExpression3(trimmed);
5046
+ const parsed = preParsed ?? parseExpression4(trimmed);
4808
5047
  const support = isSupported(parsed);
4809
5048
  if (!support.supported) {
4810
5049
  this.state.errors.push({
@@ -4822,10 +5061,15 @@ ${goFields.join(`
4822
5061
  out.parsed = parsed;
4823
5062
  return this.renderParsedExpr(parsed);
4824
5063
  }
5064
+ isLoopShadowedName(name) {
5065
+ 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));
5066
+ }
4825
5067
  resolveStaticRecordLiteralIndex(jsExpr) {
4826
5068
  const m = /^([A-Za-z_$][\w$]*)\[\s*(?:'([^']*)'|"([^"]*)")\s*\]$/.exec(jsExpr) ?? /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/.exec(jsExpr);
4827
5069
  if (!m)
4828
5070
  return null;
5071
+ if (this.isLoopShadowedName(m[1]))
5072
+ return null;
4829
5073
  const key = m[2] ?? m[3];
4830
5074
  const constInfo = (this.state.localConstants ?? []).find((c) => c.name === m[1] && c.isModule);
4831
5075
  if (constInfo?.value === undefined)
@@ -4931,7 +5175,7 @@ ${goFields.join(`
4931
5175
  }
4932
5176
  convertConditionToGo(jsCondition, preParsed) {
4933
5177
  const trimmed = jsCondition.trim();
4934
- const parsed = preParsed ?? parseExpression3(trimmed);
5178
+ const parsed = preParsed ?? parseExpression4(trimmed);
4935
5179
  const support = isSupported(parsed);
4936
5180
  if (!support.supported) {
4937
5181
  this.state.errors.push({
@@ -5152,6 +5396,29 @@ ${goFields.join(`
5152
5396
  }
5153
5397
  return;
5154
5398
  }
5399
+ wrapperDatumField(loop) {
5400
+ if (!loop.childComponent)
5401
+ return null;
5402
+ for (const prop of loop.childComponent.props) {
5403
+ if (prop.isEventHandler)
5404
+ continue;
5405
+ if (prop.value.kind !== "expression")
5406
+ continue;
5407
+ const parsed = prop.value.parsed;
5408
+ const isBareParamRef = parsed ? parsed.kind === "identifier" && parsed.name === loop.param : prop.value.expr.trim() === loop.param;
5409
+ if (isBareParamRef)
5410
+ return capitalizeFieldName(prop.name);
5411
+ }
5412
+ return null;
5413
+ }
5414
+ getBakedStaticChildLoop(markerId, childComponent, arrayParsed, param, key) {
5415
+ if (this.bakedStaticChildLoopCache.has(markerId)) {
5416
+ return this.bakedStaticChildLoopCache.get(markerId) ?? null;
5417
+ }
5418
+ const result = analyzeBakeableStaticChildLoop({ props: childComponent.props, loopArrayParsed: arrayParsed, loopParam: param, loopKey: key }, this.state.localConstants, { isNameShadowed: (name) => this.state.staticLoopSourceBoundNames.has(name) });
5419
+ this.bakedStaticChildLoopCache.set(markerId, result);
5420
+ return result;
5421
+ }
5155
5422
  renderLoop(loop) {
5156
5423
  if (loop.clientOnly) {
5157
5424
  return `{{bfComment "loop:${loop.markerId}"}}{{bfComment "/loop:${loop.markerId}"}}`;
@@ -5172,8 +5439,13 @@ ${goFields.join(`
5172
5439
  }
5173
5440
  });
5174
5441
  }
5442
+ const bakedChildLoop = loop.childComponent ? this.getBakedStaticChildLoop(loop.markerId, loop.childComponent, loop.arrayParsed, loop.param, loop.key ?? undefined) : null;
5443
+ const bakedElementLoop = loop.childComponent ? null : analyzeBakeableStaticElementLoop(loop, this.state.localConstants, { isNameShadowed: (name) => this.state.staticLoopSourceBoundNames.has(name) });
5444
+ if (bakedElementLoop) {
5445
+ return this.renderUnrolledStaticElementLoop(loop, bakedElementLoop.items);
5446
+ }
5175
5447
  const arrayName = loop.array.trim();
5176
- if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
5448
+ if (bakedChildLoop === null && /^[A-Za-z_$][\w$]*$/.test(arrayName)) {
5177
5449
  const arrayConst = this.state.localConstants.find((c) => c.name === arrayName);
5178
5450
  if (arrayConst && !arrayConst.isModule && arrayConst.parsed && !this.isStringExpr(arrayConst.parsed, new Set)) {
5179
5451
  this.state.errors.push({
@@ -5187,7 +5459,7 @@ ${goFields.join(`
5187
5459
  });
5188
5460
  }
5189
5461
  }
5190
- let goArray = this.convertExpressionToGo(loop.array);
5462
+ let goArray = loop.childComponent ? "" : this.convertExpressionToGo(loop.array);
5191
5463
  const param = loop.param;
5192
5464
  let index = loop.index || "_";
5193
5465
  let rangeIndex = index;
@@ -5255,7 +5527,8 @@ ${goFields.join(`
5255
5527
  if (loop.filterPredicate) {
5256
5528
  let filterCond;
5257
5529
  if (loop.filterPredicate.predicate) {
5258
- filterCond = this.renderPredicateCondition(loop.filterPredicate.predicate, loop.filterPredicate.param);
5530
+ const datumField = this.wrapperDatumField(loop);
5531
+ filterCond = this.renderPredicateCondition(loop.filterPredicate.predicate, loop.filterPredicate.param, datumField);
5259
5532
  } else {
5260
5533
  filterCond = "true";
5261
5534
  }
@@ -5263,6 +5536,38 @@ ${goFields.join(`
5263
5536
  }
5264
5537
  return `{{bfComment "loop:${loop.markerId}"}}{{range $${rangeIndex}, $${rangeValue} := ${goArray}}}${itemMarker}${children}{{end}}{{bfComment "/loop:${loop.markerId}"}}`;
5265
5538
  }
5539
+ renderUnrolledStaticElementLoop(loop, items) {
5540
+ this.inLoop = true;
5541
+ this.loopWrapperStack.push(false);
5542
+ this.loopKeyDepthStack.push(loop.depth);
5543
+ this.loopScalarItemStack.push(this.scalarLiteralLoopGoType(loop.arrayParsed, loop.itemType) !== null);
5544
+ this.loopParamStack.push(loop.param);
5545
+ let body = "";
5546
+ for (const item of items) {
5547
+ this.staticLoopItemStack.push({ param: loop.param, item });
5548
+ body += this.renderChildren(loop.children);
5549
+ this.staticLoopItemStack.pop();
5550
+ if (this.staticLoopBakeFailed) {
5551
+ this.staticLoopBakeFailed = false;
5552
+ this.state.errors.push({
5553
+ code: "BF101",
5554
+ severity: "error",
5555
+ 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.`,
5556
+ loc: loop.loc ?? this.makeLoc(),
5557
+ suggestion: {
5558
+ 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."
5559
+ }
5560
+ });
5561
+ break;
5562
+ }
5563
+ }
5564
+ this.loopParamStack.pop();
5565
+ this.loopScalarItemStack.pop();
5566
+ this.loopKeyDepthStack.pop();
5567
+ this.loopWrapperStack.pop();
5568
+ this.inLoop = false;
5569
+ return `{{bfComment "loop:${loop.markerId}"}}${body}{{bfComment "/loop:${loop.markerId}"}}`;
5570
+ }
5266
5571
  loopItemMarker(loop) {
5267
5572
  if (loop.bodyIsMultiRoot)
5268
5573
  return `{{bfComment "bf-loop-i"}}`;
@@ -5382,7 +5687,7 @@ ${children}`;
5382
5687
  const body = name.startsWith("aria-") ? `${name}="true"` : name;
5383
5688
  return `${preamble}{{if ${goCond}}}${body}{{end}}`;
5384
5689
  }
5385
- const parsed = value.parsed ?? parseExpression3(value.expr.trim());
5690
+ const parsed = value.parsed ?? parseExpression4(value.expr.trim());
5386
5691
  if (parsed.kind === "conditional") {
5387
5692
  const undef = (e) => e.kind === "identifier" && (e.name === "undefined" || e.name === "null") || e.kind === "literal" && (e.value === null || e.value === undefined);
5388
5693
  const test = parsed.test;
@@ -5447,7 +5752,7 @@ ${children}`;
5447
5752
  if (!entries)
5448
5753
  return null;
5449
5754
  for (const e of entries) {
5450
- if (e.kind === "expr" && !isSupported(parseExpression3(e.expr)).supported)
5755
+ if (e.kind === "expr" && !isSupported(parseExpression4(e.expr)).supported)
5451
5756
  return null;
5452
5757
  }
5453
5758
  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 +5762,8 @@ ${children}`;
5457
5762
  for (const attr of element.attrs) {
5458
5763
  if (attr.clientOnly)
5459
5764
  continue;
5765
+ if (isDangerousInnerHtmlAttr(attr))
5766
+ continue;
5460
5767
  let attrName;
5461
5768
  if (attr.name === "className")
5462
5769
  attrName = "class";
@@ -46,6 +46,14 @@ export declare class CompileState {
46
46
  * `Record`-index lookups without re-threading the full `ir` through helpers.
47
47
  */
48
48
  localConstants: IRMetadata['localConstants'];
49
+ /**
50
+ * Every name a `.map()`/`.filter()` loop callback binds as its item/index
51
+ * parameter anywhere in the component (#2208 fable review). Consulted by
52
+ * static loop-source resolution (`getBakedStaticChildLoop`) so a const
53
+ * whose name a DIFFERENT, enclosing loop's own callback param shadows is
54
+ * never resolved as that const's static value.
55
+ */
56
+ staticLoopSourceBoundNames: Set<string>;
49
57
  /**
50
58
  * Names of component-scope arrow-const helpers (`const sortClass = …`),
51
59
  * eligible for call-site inlining.
@@ -1 +1 @@
1
- {"version":3,"file":"compile-state.d.ts","sourceRoot":"","sources":["../../../src/adapter/lib/compile-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EACf,eAAe,EACf,UAAU,EACV,MAAM,EACN,eAAe,EACf,QAAQ,EACR,WAAW,EACX,cAAc,EACd,QAAQ,EACT,MAAM,iBAAiB,CAAA;AAExB,qBAAa,YAAY;IAGvB,aAAa,EAAE,MAAM,CAAK;IAC1B,MAAM,EAAE,aAAa,EAAE,CAAK;IAE5B;;+CAE2C;IAC3C,uBAAuB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAY;IAEhD,kBAAkB,EAAE,MAAM,CAAI;IAE9B;;;OAGG;IACH,sBAAsB,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAK;IAErE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAO;IAErC;;;;OAIG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAO;IAEnC;;;;;OAKG;IACH,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAY;IAEnD;;;;OAIG;IACH,cAAc,EAAE,UAAU,CAAC,gBAAgB,CAAC,CAAK;IAEjD;;;OAGG;IACH,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAY;IAEzC;;qFAEiF;IACjF,YAAY,EAAE,QAAQ,EAAE,CAAK;IAE7B,0FAA0F;IAC1F,sBAAsB,EAAE,cAAc,EAAE,CAAK;IAE7C;;;OAGG;IACH,gBAAgB,EAAE,eAAe,EAAE,CAAK;IAExC;;;OAGG;IACH,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAY;IAE3C;;;;;;OAMG;IACH,uBAAuB,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAY;IAEjE;;;;;;;;OAQG;IACH,WAAW,EAAE,WAAW,CAA+B;IAEvD;;;;;;OAMG;IACH,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAY;IAElD;;;;;;OAMG;IACH,gBAAgB,EAAE,eAAe,EAAE,CAAK;IAExC;;;;OAIG;IACH,iBAAiB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAY;IAE1C;;;;;OAKG;IACH,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAY;IAEzC;gBACY;IACZ,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAY;IAEvC;;4DAEwD;IACxD,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAY;IAIpD;sEACkE;IAClE,gBAAgB,EAAE,OAAO,CAAQ;IAEjC;sCACkC;IAClC,OAAO,EAAE,OAAO,CAAQ;IAExB,uFAAuF;IACvF,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAY;IAEvC,mFAAmF;IACnF,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAY;IAEjD;;;;OAIG;IACH,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAY;IAE/D;;;OAGG;IACH,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAY;IAEnD;yEACqE;IACrE,kBAAkB,UAAQ;CAC3B"}
1
+ {"version":3,"file":"compile-state.d.ts","sourceRoot":"","sources":["../../../src/adapter/lib/compile-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EACf,eAAe,EACf,UAAU,EACV,MAAM,EACN,eAAe,EACf,QAAQ,EACR,WAAW,EACX,cAAc,EACd,QAAQ,EACT,MAAM,iBAAiB,CAAA;AAExB,qBAAa,YAAY;IAGvB,aAAa,EAAE,MAAM,CAAK;IAC1B,MAAM,EAAE,aAAa,EAAE,CAAK;IAE5B;;+CAE2C;IAC3C,uBAAuB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAY;IAEhD,kBAAkB,EAAE,MAAM,CAAI;IAE9B;;;OAGG;IACH,sBAAsB,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAK;IAErE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAO;IAErC;;;;OAIG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAO;IAEnC;;;;;OAKG;IACH,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAY;IAEnD;;;;OAIG;IACH,cAAc,EAAE,UAAU,CAAC,gBAAgB,CAAC,CAAK;IAEjD;;;;;;OAMG;IACH,0BAA0B,EAAE,GAAG,CAAC,MAAM,CAAC,CAAY;IAEnD;;;OAGG;IACH,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAY;IAEzC;;qFAEiF;IACjF,YAAY,EAAE,QAAQ,EAAE,CAAK;IAE7B,0FAA0F;IAC1F,sBAAsB,EAAE,cAAc,EAAE,CAAK;IAE7C;;;OAGG;IACH,gBAAgB,EAAE,eAAe,EAAE,CAAK;IAExC;;;OAGG;IACH,kBAAkB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAY;IAE3C;;;;;;OAMG;IACH,uBAAuB,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAY;IAEjE;;;;;;;;OAQG;IACH,WAAW,EAAE,WAAW,CAA+B;IAEvD;;;;;;OAMG;IACH,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAY;IAElD;;;;;;OAMG;IACH,gBAAgB,EAAE,eAAe,EAAE,CAAK;IAExC;;;;OAIG;IACH,iBAAiB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAY;IAE1C;;;;;OAKG;IACH,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAY;IAEzC;gBACY;IACZ,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAY;IAEvC;;4DAEwD;IACxD,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAY;IAIpD;sEACkE;IAClE,gBAAgB,EAAE,OAAO,CAAQ;IAEjC;sCACkC;IAClC,OAAO,EAAE,OAAO,CAAQ;IAExB,uFAAuF;IACvF,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAY;IAEvC,mFAAmF;IACnF,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAY;IAEjD;;;;OAIG;IACH,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAY;IAE/D;;;OAGG;IACH,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAY;IAEnD;yEACqE;IACrE,kBAAkB,UAAQ;CAC3B"}