@barefootjs/go-template 0.34.0 → 0.35.1

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/vite.js CHANGED
@@ -8,7 +8,7 @@ import { devModuleUrl, loadManifest, resolveDevOrigin, resolveScriptAssets, toPo
8
8
  import ts25 from "typescript";
9
9
 
10
10
  // ../jsx/src/analyzer.ts
11
- import ts10 from "typescript";
11
+ import ts11 from "typescript";
12
12
 
13
13
  // ../jsx/src/expression-parser.ts
14
14
  import ts from "typescript";
@@ -2250,7 +2250,33 @@ function propsDestructureBinding(p) {
2250
2250
  const binding = callerKey === localName ? localName : `${isIdentifierName(callerKey) ? callerKey : JSON.stringify(callerKey)}: ${localName}`;
2251
2251
  return p.defaultValue ? `${binding} = ${p.defaultValue}` : binding;
2252
2252
  }
2253
+ function resolveBodyDestructuredPropAliases(localConstants, propsObjectName) {
2254
+ const aliases = new Map;
2255
+ if (propsObjectName === null)
2256
+ return aliases;
2257
+ for (const c of localConstants) {
2258
+ if (c.isModule)
2259
+ continue;
2260
+ const m = c.parsed;
2261
+ if (m?.kind === "member" && !m.computed && m.object.kind === "identifier" && m.object.name === propsObjectName) {
2262
+ aliases.set(c.name, m.property);
2263
+ }
2264
+ }
2265
+ return aliases;
2266
+ }
2253
2267
  var EMPTY_SET = new Set;
2268
+ function resolveAliasOrigin(constantValues, name, terminal) {
2269
+ const visited = new Set;
2270
+ let current = name.trim();
2271
+ while (current !== undefined && !visited.has(current)) {
2272
+ const hit = terminal(current);
2273
+ if (hit !== null)
2274
+ return hit;
2275
+ visited.add(current);
2276
+ current = constantValues.get(current)?.trim();
2277
+ }
2278
+ return null;
2279
+ }
2254
2280
 
2255
2281
  // ../jsx/src/ir-to-client-js/csr-substitute.ts
2256
2282
  function extractFreeIdentifiersFromText(text) {
@@ -2263,6 +2289,35 @@ function extractFreeIdentifiersFromText(text) {
2263
2289
  const expr = ts5.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
2264
2290
  return extractFreeIdentifiersFromNode(expr);
2265
2291
  }
2292
+ function resolveGetterAliases(localConstants, isGetter) {
2293
+ const constantValues = new Map;
2294
+ for (const c of localConstants) {
2295
+ if (c.isModule)
2296
+ continue;
2297
+ constantValues.set(c.name, c.value);
2298
+ }
2299
+ const aliases = new Map;
2300
+ for (const c of localConstants) {
2301
+ if (c.isModule || isGetter(c.name))
2302
+ continue;
2303
+ const origin = resolveAliasOrigin(constantValues, c.name, (current) => isGetter(current) ? current : null);
2304
+ if (origin !== null && origin !== c.name)
2305
+ aliases.set(c.name, origin);
2306
+ }
2307
+ return aliases;
2308
+ }
2309
+ function collectAliasableGetterNames(signals, memos) {
2310
+ const getterNames = new Set;
2311
+ for (const sig of signals) {
2312
+ if (sig.getter && !sig.isModule && !sig.envReader)
2313
+ getterNames.add(sig.getter);
2314
+ }
2315
+ for (const memo of memos) {
2316
+ if (!memo.isModule)
2317
+ getterNames.add(memo.name);
2318
+ }
2319
+ return getterNames;
2320
+ }
2266
2321
 
2267
2322
  // ../jsx/src/ir-to-client-js/rewrite-props-object.ts
2268
2323
  import ts6 from "typescript";
@@ -2344,6 +2399,12 @@ class BindingScope {
2344
2399
  }
2345
2400
  }
2346
2401
 
2402
+ // ../jsx/src/ir-to-client-js/safe-html.ts
2403
+ function safeHtml(expr) {
2404
+ return expr;
2405
+ }
2406
+ var EMPTY_MARKUP = safeHtml("''");
2407
+
2347
2408
  // ../jsx/src/ir-to-client-js/html-template.ts
2348
2409
  var VOID_ELEMENTS = new Set([
2349
2410
  "area",
@@ -2401,14 +2462,17 @@ function freshCounters() {
2401
2462
  }
2402
2463
 
2403
2464
  // ../jsx/src/analyzer-context.ts
2404
- import ts9 from "typescript";
2465
+ import ts10 from "typescript";
2405
2466
 
2406
2467
  // ../jsx/src/strip-types.ts
2407
2468
  import ts8 from "typescript";
2408
2469
 
2470
+ // ../jsx/src/reactivity-checker.ts
2471
+ import ts9 from "typescript";
2472
+
2409
2473
  // ../jsx/src/analyzer-context.ts
2410
- var _typePrinter = ts9.createPrinter({ removeComments: true, omitTrailingSemicolon: true });
2411
- var _blankTypeSourceFile = ts9.createSourceFile("__bf_types__.ts", "", ts9.ScriptTarget.Latest);
2474
+ var _typePrinter = ts10.createPrinter({ removeComments: true, omitTrailingSemicolon: true });
2475
+ var _blankTypeSourceFile = ts10.createSourceFile("__bf_types__.ts", "", ts10.ScriptTarget.Latest);
2412
2476
 
2413
2477
  // ../jsx/src/errors.ts
2414
2478
  var ErrorCodes = {
@@ -2630,6 +2694,7 @@ var CLIENT_EXPORTS = new Set([
2630
2694
  "isSSRPortal",
2631
2695
  "findSiblingSlot",
2632
2696
  "cleanupPortalPlaceholder",
2697
+ "trackPosition",
2633
2698
  "createSearchParams",
2634
2699
  "queryHref",
2635
2700
  "formatDate",
@@ -2640,46 +2705,46 @@ function extractFreeIdentifiersFromNode(node) {
2640
2705
  const ids = new Set;
2641
2706
  const boundNames = new Set;
2642
2707
  function addBindingNames(name, out) {
2643
- if (ts10.isIdentifier(name))
2708
+ if (ts11.isIdentifier(name))
2644
2709
  out.push(name.text);
2645
- else if (ts10.isObjectBindingPattern(name))
2710
+ else if (ts11.isObjectBindingPattern(name))
2646
2711
  name.elements.forEach((e) => addBindingNames(e.name, out));
2647
- else if (ts10.isArrayBindingPattern(name))
2712
+ else if (ts11.isArrayBindingPattern(name))
2648
2713
  name.elements.forEach((e) => {
2649
- if (!ts10.isOmittedExpression(e))
2714
+ if (!ts11.isOmittedExpression(e))
2650
2715
  addBindingNames(e.name, out);
2651
2716
  });
2652
2717
  }
2653
2718
  function visit(n) {
2654
- if (ts10.isTypeNode(n))
2719
+ if (ts11.isTypeNode(n))
2655
2720
  return;
2656
- if (ts10.isIdentifier(n)) {
2721
+ if (ts11.isIdentifier(n)) {
2657
2722
  const parent = n.parent;
2658
- if (parent && ts10.isPropertyAccessExpression(parent) && parent.name === n)
2723
+ if (parent && ts11.isPropertyAccessExpression(parent) && parent.name === n)
2659
2724
  return;
2660
- if (parent && ts10.isPropertyAssignment(parent) && parent.name === n)
2725
+ if (parent && ts11.isPropertyAssignment(parent) && parent.name === n)
2661
2726
  return;
2662
- if (parent && ts10.isParameter(parent) && parent.name === n)
2727
+ if (parent && ts11.isParameter(parent) && parent.name === n)
2663
2728
  return;
2664
- if (parent && ts10.isVariableDeclaration(parent) && parent.name === n)
2729
+ if (parent && ts11.isVariableDeclaration(parent) && parent.name === n)
2665
2730
  return;
2666
2731
  if (boundNames.has(n.text))
2667
2732
  return;
2668
2733
  ids.add(n.text);
2669
2734
  return;
2670
2735
  }
2671
- if (ts10.isArrowFunction(n)) {
2736
+ if (ts11.isArrowFunction(n)) {
2672
2737
  const params = [];
2673
2738
  for (const p of n.parameters)
2674
2739
  addBindingNames(p.name, params);
2675
2740
  for (const name of params)
2676
2741
  boundNames.add(name);
2677
- ts10.forEachChild(n, visit);
2742
+ ts11.forEachChild(n, visit);
2678
2743
  for (const name of params)
2679
2744
  boundNames.delete(name);
2680
2745
  return;
2681
2746
  }
2682
- ts10.forEachChild(n, visit);
2747
+ ts11.forEachChild(n, visit);
2683
2748
  }
2684
2749
  visit(node);
2685
2750
  return ids;
@@ -2690,7 +2755,8 @@ var BROWSER_ONLY_CLIENT_APIS = new Set([
2690
2755
  "createPortal",
2691
2756
  "isSSRPortal",
2692
2757
  "findSiblingSlot",
2693
- "cleanupPortalPlaceholder"
2758
+ "cleanupPortalPlaceholder",
2759
+ "trackPosition"
2694
2760
  ]);
2695
2761
  var REACTIVE_PRIMITIVES = new Set([
2696
2762
  "createSignal",
@@ -2752,7 +2818,7 @@ function preambleAnalysisTemplateText(p) {
2752
2818
  }
2753
2819
 
2754
2820
  // ../jsx/src/module-exports.ts
2755
- import ts11 from "typescript";
2821
+ import ts12 from "typescript";
2756
2822
  function formatParamWithType(p) {
2757
2823
  const rest = p.isRest ? "..." : "";
2758
2824
  const optional = p.optional ? "?" : "";
@@ -2787,21 +2853,21 @@ function findAssignedNames(bodyText, candidates) {
2787
2853
  const assigned = new Set;
2788
2854
  if (candidates.size === 0)
2789
2855
  return assigned;
2790
- const sf = ts11.createSourceFile("bf-assignment-scan.tsx", bodyText, ts11.ScriptTarget.Latest, false, ts11.ScriptKind.TSX);
2856
+ const sf = ts12.createSourceFile("bf-assignment-scan.tsx", bodyText, ts12.ScriptTarget.Latest, false, ts12.ScriptKind.TSX);
2791
2857
  const record = (target) => {
2792
- if (ts11.isIdentifier(target) && candidates.has(target.text)) {
2858
+ if (ts12.isIdentifier(target) && candidates.has(target.text)) {
2793
2859
  assigned.add(target.text);
2794
2860
  }
2795
2861
  };
2796
2862
  const visit = (node) => {
2797
- if (ts11.isBinaryExpression(node) && isAssignmentOperator(node.operatorToken.kind)) {
2863
+ if (ts12.isBinaryExpression(node) && isAssignmentOperator(node.operatorToken.kind)) {
2798
2864
  record(node.left);
2799
- } else if ((ts11.isPrefixUnaryExpression(node) || ts11.isPostfixUnaryExpression(node)) && (node.operator === ts11.SyntaxKind.PlusPlusToken || node.operator === ts11.SyntaxKind.MinusMinusToken)) {
2865
+ } else if ((ts12.isPrefixUnaryExpression(node) || ts12.isPostfixUnaryExpression(node)) && (node.operator === ts12.SyntaxKind.PlusPlusToken || node.operator === ts12.SyntaxKind.MinusMinusToken)) {
2800
2866
  record(node.operand);
2801
2867
  }
2802
- ts11.forEachChild(node, visit);
2868
+ ts12.forEachChild(node, visit);
2803
2869
  };
2804
- ts11.forEachChild(sf, visit);
2870
+ ts12.forEachChild(sf, visit);
2805
2871
  return assigned;
2806
2872
  }
2807
2873
  function closeOverWritersOfMutableBindings(primaryRefs, declarations, mutableNames) {
@@ -2824,12 +2890,9 @@ function closeOverWritersOfMutableBindings(primaryRefs, declarations, mutableNam
2824
2890
  return reachable;
2825
2891
  }
2826
2892
  function isAssignmentOperator(kind) {
2827
- return kind >= ts11.SyntaxKind.FirstAssignment && kind <= ts11.SyntaxKind.LastAssignment;
2893
+ return kind >= ts12.SyntaxKind.FirstAssignment && kind <= ts12.SyntaxKind.LastAssignment;
2828
2894
  }
2829
2895
 
2830
- // ../jsx/src/reactivity-checker.ts
2831
- import ts12 from "typescript";
2832
-
2833
2896
  // ../jsx/src/free-refs.ts
2834
2897
  import ts13 from "typescript";
2835
2898
  var _bindingMapCache = new WeakMap;
@@ -5568,6 +5631,8 @@ class CompileState {
5568
5631
  restPropsName = null;
5569
5632
  moduleStringConsts = new Map;
5570
5633
  localConstants = [];
5634
+ getterAliases = new Map;
5635
+ propDestructureAliases = new Map;
5571
5636
  staticLoopSourceBoundNames = new Set;
5572
5637
  localHelperNames = new Set;
5573
5638
  currentMemos = [];
@@ -6118,21 +6183,38 @@ function lowerUrlGuard(ctx, g) {
6118
6183
  if (isBoolShape) {
6119
6184
  return ctx.convertConditionToGo(stringifyParsedExpr(g), g).condition;
6120
6185
  }
6121
- const valueGo = wrapIfMultiToken(ctx.convertExpressionToGo(stringifyParsedExpr(g), undefined, g));
6186
+ const valueGo = lowerValueOperand(ctx, g);
6122
6187
  return `ne ${valueGo} ""`;
6123
6188
  }
6124
6189
  function lowerTernary(ctx, test, consequent, alternate) {
6125
6190
  const t = lowerTernaryTest(ctx, test);
6126
- return `(bf_ternary ${t} ${lowerTernaryOperand(ctx, consequent)} ${lowerTernaryOperand(ctx, alternate)})`;
6191
+ return `(bf_ternary ${t} ${lowerValueOperand(ctx, consequent)} ${lowerValueOperand(ctx, alternate)})`;
6127
6192
  }
6128
- function lowerTernaryOperand(ctx, n) {
6193
+ function lowerTemplateLiteralValue(ctx, parts) {
6194
+ const terms = [];
6195
+ for (const part of parts) {
6196
+ if (part.type === "string") {
6197
+ if (part.value !== "")
6198
+ terms.push(`"${escapeGoString(part.value)}"`);
6199
+ } else {
6200
+ terms.push(lowerValueOperand(ctx, part.expr));
6201
+ }
6202
+ }
6203
+ if (terms.length === 0)
6204
+ return '""';
6205
+ return terms.slice(1).reduce((acc, t) => `(bf_concat_str ${acc} ${t})`, terms[0]);
6206
+ }
6207
+ function lowerValueOperand(ctx, n) {
6129
6208
  if (n.kind === "conditional") {
6130
6209
  return lowerTernary(ctx, n.test, n.consequent, n.alternate);
6131
6210
  }
6211
+ if (n.kind === "template-literal") {
6212
+ return wrapIfMultiToken(lowerTemplateLiteralValue(ctx, n.parts));
6213
+ }
6132
6214
  return wrapIfMultiToken(ctx.convertExpressionToGo(stringifyParsedExpr(n), undefined, n));
6133
6215
  }
6134
6216
  function lowerTernaryTest(ctx, test) {
6135
- const go = wrapIfMultiToken(ctx.convertExpressionToGo(stringifyParsedExpr(test), undefined, test));
6217
+ const go = lowerValueOperand(ctx, test);
6136
6218
  const isBoolShape = test.kind === "binary" && BOOL_COMPARISON_OPS.has(test.op) || test.kind === "unary" && test.op === "!" || test.kind === "literal" && test.literalType === "boolean";
6137
6219
  return isBoolShape ? go : `(bf_truthy ${go})`;
6138
6220
  }
@@ -6193,18 +6275,16 @@ function renderLoweringNode(ctx, node) {
6193
6275
  const helper = goHelperName(node.helper);
6194
6276
  if (!helper)
6195
6277
  return null;
6196
- const lowerExpr = (n) => ctx.convertExpressionToGo(stringifyParsedExpr(n), undefined, n);
6197
- const lowerArg = (n) => n.kind === "conditional" ? lowerTernary(ctx, n.test, n.consequent, n.alternate) : wrapIfMultiToken(lowerExpr(n));
6198
6278
  if (node.kind === "helper-call") {
6199
- const args = node.args.map((a) => lowerArg(a));
6279
+ const args = node.args.map((a) => lowerValueOperand(ctx, a));
6200
6280
  return [helper, ...args].join(" ");
6201
6281
  }
6202
- const parts = [wrapIfMultiToken(lowerExpr(node.base))];
6282
+ const parts = [lowerValueOperand(ctx, node.base)];
6203
6283
  for (const t of node.triples) {
6204
6284
  const includeGo = t.guard === null ? "true" : lowerUrlGuard(ctx, t.guard);
6205
6285
  parts.push(`(${includeGo})`);
6206
6286
  parts.push(JSON.stringify(t.key));
6207
- parts.push(wrapIfMultiToken(lowerExpr(t.value)));
6287
+ parts.push(lowerValueOperand(ctx, t.value));
6208
6288
  }
6209
6289
  return `${helper} ${parts.join(" ")}`;
6210
6290
  }
@@ -8195,6 +8275,11 @@ class GoTemplateAdapter extends BaseAdapter {
8195
8275
  this.state.objectTypedPropNames = new Set((ir.metadata.propsParams ?? []).filter((p) => p.type.kind === "object").map((p) => p.name));
8196
8276
  this.state.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
8197
8277
  this.state.localConstants = ir.metadata.localConstants ?? [];
8278
+ {
8279
+ const getterNames = collectAliasableGetterNames(ir.metadata.signals ?? [], ir.metadata.memos ?? []);
8280
+ this.state.getterAliases = resolveGetterAliases(ir.metadata.localConstants ?? [], (n) => getterNames.has(n));
8281
+ }
8282
+ this.state.propDestructureAliases = resolveBodyDestructuredPropAliases(ir.metadata.localConstants ?? [], ir.metadata.propsObjectName);
8198
8283
  this.state.staticLoopSourceBoundNames = collectLoopBoundNames(ir);
8199
8284
  this.bakedStaticChildLoopCache = new Map;
8200
8285
  this.state.localHelperNames = new Set(this.state.localConstants.filter((c) => !c.isModule && c.containsArrow).map((c) => c.name));
@@ -10431,9 +10516,10 @@ ${goFields.join(`
10431
10516
  return hit !== null && hit.depth > 0 && hit.binding.source === "item";
10432
10517
  }
10433
10518
  rootFieldRef(name) {
10434
- this.state.templateReadRootFields.add(name);
10519
+ const resolved = this.state.getterAliases.get(name) ?? this.state.propDestructureAliases.get(name) ?? name;
10520
+ this.state.templateReadRootFields.add(resolved);
10435
10521
  const prefix = this.inLoop ? "$." : ".";
10436
- return `${prefix}${capitalizeFieldName(name)}`;
10522
+ return `${prefix}${capitalizeFieldName(resolved)}`;
10437
10523
  }
10438
10524
  searchParamsFieldRef(name) {
10439
10525
  return this.state.searchParamsLocals.has(name) ? this.rootFieldRef("searchParams") : null;
@@ -10569,11 +10655,17 @@ ${goFields.join(`
10569
10655
  return `${obj}.${goFieldNameForKey(property)}`;
10570
10656
  }
10571
10657
  indexAccess(object, index, emit) {
10572
- return `bf_get ${wrapIfMultiToken(emit(object))} ${wrapIfMultiToken(emit(index))}`;
10658
+ return `bf_get ${wrapIfMultiToken(this.emitOperand(object, emit))} ${wrapIfMultiToken(this.emitOperand(index, emit))}`;
10659
+ }
10660
+ emitOperand(n, emit) {
10661
+ if (n.kind === "template-literal") {
10662
+ return wrapIfMultiToken(lowerTemplateLiteralValue(this.emitCtx, n.parts));
10663
+ }
10664
+ return emit(n);
10573
10665
  }
10574
10666
  binary(op, left, right, emit) {
10575
- const l = emit(left);
10576
- const r = emit(right);
10667
+ const l = this.emitOperand(left, emit);
10668
+ const r = this.emitOperand(right, emit);
10577
10669
  const wl = wrapIfMultiToken(l);
10578
10670
  const wr = wrapIfMultiToken(r);
10579
10671
  switch (op) {
@@ -10619,16 +10711,16 @@ ${goFields.join(`
10619
10711
  return this.state.stringValueNames.has(name);
10620
10712
  }
10621
10713
  unary(op, argument, emit) {
10622
- const arg = emit(argument);
10714
+ const arg = this.emitOperand(argument, emit);
10623
10715
  if (op === "!")
10624
- return `not ${arg}`;
10716
+ return `not ${wrapIfMultiToken(arg)}`;
10625
10717
  if (op === "-")
10626
10718
  return `bf_neg ${arg}`;
10627
10719
  return arg;
10628
10720
  }
10629
10721
  logical(op, left, right, emit) {
10630
- const wrapLeft = wrapIfMultiToken(emit(left));
10631
- const wrapRight = wrapIfMultiToken(emit(right));
10722
+ const wrapLeft = wrapIfMultiToken(this.emitOperand(left, emit));
10723
+ const wrapRight = wrapIfMultiToken(this.emitOperand(right, emit));
10632
10724
  if (op === "&&")
10633
10725
  return `and ${wrapLeft} ${wrapRight}`;
10634
10726
  if (op === "??" && this.nillablePropNameOf(left) !== null) {
@@ -10793,7 +10885,8 @@ ${goFields.join(`
10793
10885
  }
10794
10886
  return this.pushCallbackBF101(method, true);
10795
10887
  }
10796
- arrayMethod(method, object, args, emit) {
10888
+ arrayMethod(method, object, args, rawEmit) {
10889
+ const emit = (e) => this.emitOperand(e, rawEmit);
10797
10890
  switch (method) {
10798
10891
  case "join": {
10799
10892
  const obj = emit(object);
@@ -11383,11 +11476,9 @@ ${goFields.join(`
11383
11476
  }
11384
11477
  continue;
11385
11478
  }
11386
- const singlePartTemplateLiteral = exprOut.parsed?.kind === "template-literal" && exprOut.parsed.parts.length === 1 && exprOut.parsed.parts[0].type !== "string" && go.startsWith("{{") && go.endsWith("}}");
11387
- if (singlePartTemplateLiteral) {
11388
- go = go.slice(2, -2);
11389
- }
11390
- if (!singlePartTemplateLiteral && this.isTemplateFragment(go, exprOut.parsed?.kind)) {
11479
+ if (exprOut.parsed?.kind === "template-literal" || exprOut.parsed?.kind === "conditional") {
11480
+ go = lowerValueOperand(this.emitCtx, exprOut.parsed);
11481
+ } else if (this.isTemplateFragment(go, exprOut.parsed?.kind)) {
11391
11482
  this.state.errors.push({
11392
11483
  code: "BF101",
11393
11484
  severity: "error",
@@ -11672,7 +11763,7 @@ ${goFields.join(`
11672
11763
  case "unary": {
11673
11764
  const arg = this.renderConditionExpr(expr.argument);
11674
11765
  if (expr.op === "!")
11675
- return { preamble: arg.preamble, expr: `not ${arg.expr}` };
11766
+ return { preamble: arg.preamble, expr: `not ${wrapIfMultiToken(arg.expr)}` };
11676
11767
  if (expr.op === "-")
11677
11768
  return { preamble: arg.preamble, expr: `bf_neg ${arg.expr}` };
11678
11769
  return arg;
@@ -11690,7 +11781,7 @@ ${goFields.join(`
11690
11781
  return plain(lowerTernary(this.emitCtx, expr.test, expr.consequent, expr.alternate));
11691
11782
  }
11692
11783
  case "template-literal":
11693
- return plain(this.renderParsedExpr(expr));
11784
+ return plain(lowerTemplateLiteralValue(this.emitCtx, expr.parts));
11694
11785
  case "arrow":
11695
11786
  return plain("[ARROW-FN]");
11696
11787
  case "regex":
@@ -11917,7 +12008,7 @@ ${goFields.join(`
11917
12008
  if (loop.bodyIsMultiRoot)
11918
12009
  return `{{bfComment "loop-i"}}`;
11919
12010
  if (loop.bodyIsItemConditional && loop.key) {
11920
- return `{{bfComment (printf "loop-i:%v" ${this.convertExpressionToGo(loop.key)})}}`;
12011
+ return `{{bfComment (printf "loop-i:%v" (bfEscapeCommentKey ${this.convertExpressionToGo(loop.key)}))}}`;
11921
12012
  }
11922
12013
  return "";
11923
12014
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/go-template",
3
- "version": "0.34.0",
3
+ "version": "0.35.1",
4
4
  "description": "Go html/template adapter for BarefootJS - generates Go template files from IR",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -49,7 +49,7 @@
49
49
  "directory": "packages/adapter-go-template"
50
50
  },
51
51
  "dependencies": {
52
- "@barefootjs/shared": "0.34.0"
52
+ "@barefootjs/shared": "0.35.1"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@barefootjs/jsx": ">=0.2.0",
@@ -67,9 +67,9 @@
67
67
  },
68
68
  "devDependencies": {
69
69
  "@barefootjs/adapter-tests": "0.1.0",
70
- "@barefootjs/client": "0.34.0",
71
- "@barefootjs/jsx": "0.34.0",
72
- "@barefootjs/vite": "0.34.0",
70
+ "@barefootjs/client": "0.35.1",
71
+ "@barefootjs/jsx": "0.35.1",
72
+ "@barefootjs/vite": "0.35.1",
73
73
  "vite": "^6.0.0"
74
74
  }
75
75
  }
@@ -376,6 +376,161 @@ describe('GoTemplateAdapter - bf_ternary value-position lowering (#2335)', () =>
376
376
  test('emits no {{if}} action fragment for a value-position ternary', () => {
377
377
  expect(render("flag ? 'a' : 'b'")).not.toContain('{{if')
378
378
  })
379
+
380
+ // #2863: a multi-part template literal (mixed literal text and MULTIPLE
381
+ // interpolations) as a ternary BRANCH used to lower through the TEXT-
382
+ // position `templateLiteral()` path (`{{.Start}}-{{.End}}`), nesting raw
383
+ // `{{`/`}}` delimiters inside `bf_ternary`'s own argument list — a
384
+ // `html/template` parse error ("unexpected \"{\" in operand") at Go
385
+ // application startup, even though `bf build` itself succeeded. It must
386
+ // instead fold to one pipeline value, chained through the same
387
+ // `bf_concat_str` runtime helper JS string-concat `+` already uses (#2168).
388
+ test('a multi-part template-literal branch folds to a bf_concat_str chain, not a raw fragment (#2863)', () => {
389
+ expect(render('end ? `${start}-${end}` : start')).toBe(
390
+ '{{(bf_ternary (bf_truthy .End) (bf_concat_str (bf_concat_str .Start "-") .End) .Start)}}',
391
+ )
392
+ })
393
+
394
+ test('a template-literal branch never leaks a raw {{ or }} into the surrounding action (#2863)', () => {
395
+ const out = render('end ? `${start}-${end}` : start')
396
+ const inner = out.slice(2, -2) // strip the outer {{ }} the whole expression is wrapped in
397
+ expect(inner).not.toContain('{{')
398
+ expect(inner).not.toContain('}}')
399
+ })
400
+
401
+ test('a single-part template-literal branch reduces to the bare value, no bf_concat_str wrap (#2863)', () => {
402
+ expect(render('end ? `${start}` : end')).toBe('{{(bf_ternary (bf_truthy .End) .Start .End)}}')
403
+ })
404
+
405
+ test('static text in a template-literal branch is an escaped Go string literal (#2863)', () => {
406
+ expect(render('end ? `say "hi" ${start}` : end')).toBe(
407
+ '{{(bf_ternary (bf_truthy .End) (bf_concat_str "say \\"hi\\" " .Start) .End)}}',
408
+ )
409
+ })
410
+
411
+ test('a nested ternary inside a template-literal branch still lowers to bf_ternary, not {{if}} (#2863)', () => {
412
+ const out = render('end ? `x ${flag ? \'a\' : \'b\'}` : end')
413
+ expect(out).toBe(
414
+ '{{(bf_ternary (bf_truthy .End) (bf_concat_str "x " (bf_ternary (bf_truthy .Flag) "a" "b")) .End)}}',
415
+ )
416
+ expect(out).not.toContain('{{if')
417
+ })
418
+ })
419
+
420
+ // #2863 follow-up: the same value-position leak existed for a `+`/`||`
421
+ // operand and a registered-lowering (`queryHref`) argument, not just a
422
+ // `bf_ternary` branch — every such position funnels through the Go
423
+ // adapter's shared `lowerValueOperand` door (`expr/url-builder.ts`).
424
+ describe('GoTemplateAdapter - value-position template literal in other operand positions (#2863)', () => {
425
+ const adapter = new GoTemplateAdapter()
426
+ const render = (expr: string) => adapter.renderExpression({ expr } as IRExpression)
427
+
428
+ test('a template-literal operand of `+` folds through bf_concat_str, not a raw fragment', () => {
429
+ const out = render('`${a}-${b}` + a')
430
+ const inner = out.slice(2, -2)
431
+ expect(inner).not.toContain('{{')
432
+ expect(inner).toContain('bf_concat_str')
433
+ })
434
+
435
+ test('a template-literal operand of `||` folds through bf_concat_str, not a raw fragment', () => {
436
+ const out = render('a || `${a}-${b}`')
437
+ const inner = out.slice(2, -2)
438
+ expect(inner).not.toContain('{{')
439
+ })
440
+
441
+ // The issue's exact repro shape: a local, expression-bodied helper arrow
442
+ // (`eventTime`) whose body is the ternary+template-literal, called from a
443
+ // `.map()` row. A ternary written DIRECTLY inline in JSX instead compiles
444
+ // to a text-position `IRConditional` (`{{if}}…{{else}}…{{end}}`, correct
445
+ // and unaffected by this bug) — the buggy VALUE-position path is only
446
+ // reached once `inlineLocalHelperCall` substitutes the helper's body into
447
+ // the `eventTime(event)` call site and the Go adapter re-parses the
448
+ // result as a plain expression, not a JSX conditional.
449
+ test('the issue #2863 repro: a local-helper ternary+template-literal inlined in a .map() row lowers to valid Go source', () => {
450
+ const { template } = compileAndGenerate(`
451
+ 'use client'
452
+ import { createSignal } from '@barefootjs/client'
453
+ type Event = { id: string; time: string; endTime: string }
454
+ type Dashboard = { events: Event[] }
455
+ const empty: Dashboard = { events: [] }
456
+ export function Schedule() {
457
+ const [data] = createSignal<Dashboard>(empty)
458
+ const eventTime = (event: Event) =>
459
+ event.endTime ? \`\${event.time}–\${event.endTime}\` : event.time
460
+ return (
461
+ <ol>
462
+ {data().events.map((event) => (
463
+ <li key={event.id}><time>{eventTime(event)}</time></li>
464
+ ))}
465
+ </ol>
466
+ )
467
+ }
468
+ `)
469
+ expect(template).toContain(
470
+ '(bf_ternary (bf_truthy .EndTime) (bf_concat_str (bf_concat_str .Time "–") .EndTime) .Time)',
471
+ )
472
+ expect(template).not.toContain('{{.Time}}–{{.EndTime}}')
473
+ })
474
+ })
475
+
476
+ // #2863 (pullfrog review on #2877): three more argument/operand positions
477
+ // that reach a template literal without going through `lowerValueOperand`/
478
+ // `emitOperand` — verified to reproduce the identical `unexpected "{" in
479
+ // operand` class of bug before this fix, via a standalone script driving
480
+ // `html/template.Parse` on the pre-fix output.
481
+ describe('GoTemplateAdapter - value-position template literal in condition/array-method/index positions (#2863 follow-up)', () => {
482
+ // `renderConditionExpr`'s own `binary`/`unary`/`logical` recursion is a
483
+ // SEPARATE walker from the generic `emit()` dispatcher (it threads a
484
+ // `preamble` string `emitOperand` doesn't track) — reached whenever a
485
+ // ternary/`&&`/`||`/`!` TEST is a comparison against (or otherwise
486
+ // contains) a template literal, since `lowerTernaryTest`/`lowerUrlGuard`'s
487
+ // "bool-shape" branch routes through `convertConditionToGo` instead of
488
+ // `lowerValueOperand`.
489
+ test('a ternary TEST comparing to a template literal folds through bf_concat_str inside the {{if}}', () => {
490
+ const { template } = compileAndGenerate(`
491
+ 'use client'
492
+ import { createSignal } from '@barefootjs/client'
493
+ export function T() {
494
+ const [x] = createSignal('x')
495
+ const [y] = createSignal('y')
496
+ const [z] = createSignal('z')
497
+ return <span>{(x() === \`\${y()}-\${z()}\`) ? 'a' : 'b'}</span>
498
+ }
499
+ `)
500
+ expect(template).toContain('{{if eq .X (bf_concat_str (bf_concat_str .Y "-") .Z)}}')
501
+ })
502
+
503
+ // `arrayMethod()`'s per-method cases (`join`, `includes`, `indexOf`, …) all
504
+ // lower their receiver/args via the dispatcher's plain `emit`, not
505
+ // `emitOperand` — no ternary or helper-inlining needed to reach the bug.
506
+ test('Array.join with a template-literal separator folds through bf_concat_str', () => {
507
+ const { template } = compileAndGenerate(`
508
+ 'use client'
509
+ import { createSignal } from '@barefootjs/client'
510
+ export function T() {
511
+ const [items] = createSignal<string[]>([])
512
+ const [y] = createSignal('y')
513
+ const [z] = createSignal('z')
514
+ return <span>{items().join(\`\${y()}-\${z()}\`)}</span>
515
+ }
516
+ `)
517
+ expect(template).toContain('bf_join (.Items) (bf_concat_str (bf_concat_str .Y "-") .Z)')
518
+ })
519
+
520
+ // `indexAccess()` lowers both the object and the index via plain `emit`.
521
+ test('a computed index-access with a template-literal key folds through bf_concat_str', () => {
522
+ const { template } = compileAndGenerate(`
523
+ 'use client'
524
+ import { createSignal } from '@barefootjs/client'
525
+ export function T() {
526
+ const [obj] = createSignal<Record<string, string>>({})
527
+ const [y] = createSignal('y')
528
+ const [z] = createSignal('z')
529
+ return <span>{obj()[\`\${y()}-\${z()}\`]}</span>
530
+ }
531
+ `)
532
+ expect(template).toContain('bf_get .Obj (bf_concat_str (bf_concat_str .Y "-") .Z)')
533
+ })
379
534
  })
380
535
 
381
536
  // #2335 item 2 (correctness): a ternary used as a boolean CONDITION used to
@@ -4701,11 +4856,14 @@ export function CompositeRowChildComponent(props: { items: Item[] }) {
4701
4856
  })
4702
4857
 
4703
4858
  // A multi-part template literal (mixed literal text and interpolation,
4704
- // `` `#${row.id} ${row.label}` ``) has no single-pipeline-value reduction
4705
- // the way a single-part ternary doesrefuse it loudly (BF101) instead
4706
- // of silently emitting the stale constructor-only value (the #2445 bug
4707
- // this whole fix exists to close).
4708
- test('a multi-part template-literal per-row prop is refused with BF101, not silently stale', () => {
4859
+ // `` `#${row.id} ${row.label}` ``) folds through the shared value-operand
4860
+ // door (#2863) a left-folded `bf_concat_str` chaininstead of being
4861
+ // refused (BF101) the way it used to be here (this call site had no
4862
+ // reduction for the multi-part case before #2863, only a single-part
4863
+ // unwrap). It must still NOT silently emit the stale constructor-only
4864
+ // value (the #2445 bug this whole fix exists to close) — it now emits the
4865
+ // correct live per-row value instead.
4866
+ test('a multi-part template-literal per-row prop folds through bf_concat_str, not silently stale (#2445, #2863)', () => {
4709
4867
  const result = compileJSX(`
4710
4868
  'use client'
4711
4869
  import { createSignal } from '@barefootjs/client'
@@ -4726,9 +4884,11 @@ export function CompositeRowChildComponent(props: { items: Item[] }) {
4726
4884
  )
4727
4885
  }
4728
4886
  `.trimStart(), 'test.tsx', { adapter: new GoTemplateAdapter(), outputIR: false })
4729
- expect((result.errors ?? []).some(e => e.code === 'BF101')).toBe(true)
4887
+ expect(result.errors ?? []).toEqual([])
4730
4888
  const template = result.files.find(f => f.type === 'markedTemplate')!.content
4731
- expect(template).not.toContain('bf_with_props')
4889
+ expect(template).toContain(
4890
+ '{{template "Badge" (bf_with_props $.BadgeSlot0 "Text" (bf_concat_str (bf_concat_str (bf_concat_str "#" .ID) " ") .Label))}}',
4891
+ )
4732
4892
  })
4733
4893
 
4734
4894
  // A prop whose expression `convertExpressionToGo` itself refuses (an