@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/adapter/expr/url-builder.d.ts +37 -1
- package/dist/adapter/expr/url-builder.d.ts.map +1 -1
- package/dist/adapter/go-template-adapter.d.ts +18 -1
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +59 -28
- package/dist/adapter/lib/compile-state.d.ts +20 -0
- package/dist/adapter/lib/compile-state.d.ts.map +1 -1
- package/dist/conformance-pins.d.ts.map +1 -1
- package/dist/index.js +63 -35
- package/dist/render-divergences.d.ts.map +1 -1
- package/dist/vite.js +147 -56
- package/package.json +5 -5
- package/src/__tests__/go-template-adapter.test.ts +167 -7
- package/src/adapter/expr/url-builder.ts +63 -28
- package/src/adapter/go-template-adapter.ts +105 -42
- package/src/adapter/lib/compile-state.ts +22 -0
- package/src/conformance-pins.ts +13 -7
- package/src/render-divergences.ts +1 -6
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
|
|
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
|
|
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 =
|
|
2411
|
-
var _blankTypeSourceFile =
|
|
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 (
|
|
2708
|
+
if (ts11.isIdentifier(name))
|
|
2644
2709
|
out.push(name.text);
|
|
2645
|
-
else if (
|
|
2710
|
+
else if (ts11.isObjectBindingPattern(name))
|
|
2646
2711
|
name.elements.forEach((e) => addBindingNames(e.name, out));
|
|
2647
|
-
else if (
|
|
2712
|
+
else if (ts11.isArrayBindingPattern(name))
|
|
2648
2713
|
name.elements.forEach((e) => {
|
|
2649
|
-
if (!
|
|
2714
|
+
if (!ts11.isOmittedExpression(e))
|
|
2650
2715
|
addBindingNames(e.name, out);
|
|
2651
2716
|
});
|
|
2652
2717
|
}
|
|
2653
2718
|
function visit(n) {
|
|
2654
|
-
if (
|
|
2719
|
+
if (ts11.isTypeNode(n))
|
|
2655
2720
|
return;
|
|
2656
|
-
if (
|
|
2721
|
+
if (ts11.isIdentifier(n)) {
|
|
2657
2722
|
const parent = n.parent;
|
|
2658
|
-
if (parent &&
|
|
2723
|
+
if (parent && ts11.isPropertyAccessExpression(parent) && parent.name === n)
|
|
2659
2724
|
return;
|
|
2660
|
-
if (parent &&
|
|
2725
|
+
if (parent && ts11.isPropertyAssignment(parent) && parent.name === n)
|
|
2661
2726
|
return;
|
|
2662
|
-
if (parent &&
|
|
2727
|
+
if (parent && ts11.isParameter(parent) && parent.name === n)
|
|
2663
2728
|
return;
|
|
2664
|
-
if (parent &&
|
|
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 (
|
|
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
|
-
|
|
2742
|
+
ts11.forEachChild(n, visit);
|
|
2678
2743
|
for (const name of params)
|
|
2679
2744
|
boundNames.delete(name);
|
|
2680
2745
|
return;
|
|
2681
2746
|
}
|
|
2682
|
-
|
|
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
|
|
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 =
|
|
2856
|
+
const sf = ts12.createSourceFile("bf-assignment-scan.tsx", bodyText, ts12.ScriptTarget.Latest, false, ts12.ScriptKind.TSX);
|
|
2791
2857
|
const record = (target) => {
|
|
2792
|
-
if (
|
|
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 (
|
|
2863
|
+
if (ts12.isBinaryExpression(node) && isAssignmentOperator(node.operatorToken.kind)) {
|
|
2798
2864
|
record(node.left);
|
|
2799
|
-
} else if ((
|
|
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
|
-
|
|
2868
|
+
ts12.forEachChild(node, visit);
|
|
2803
2869
|
};
|
|
2804
|
-
|
|
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 >=
|
|
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 =
|
|
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} ${
|
|
6191
|
+
return `(bf_ternary ${t} ${lowerValueOperand(ctx, consequent)} ${lowerValueOperand(ctx, alternate)})`;
|
|
6127
6192
|
}
|
|
6128
|
-
function
|
|
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 =
|
|
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) =>
|
|
6279
|
+
const args = node.args.map((a) => lowerValueOperand(ctx, a));
|
|
6200
6280
|
return [helper, ...args].join(" ");
|
|
6201
6281
|
}
|
|
6202
|
-
const parts = [
|
|
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(
|
|
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.
|
|
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(
|
|
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(
|
|
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 =
|
|
10576
|
-
const r =
|
|
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 =
|
|
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(
|
|
10631
|
-
const wrapRight = wrapIfMultiToken(
|
|
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,
|
|
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
|
-
|
|
11387
|
-
|
|
11388
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
71
|
-
"@barefootjs/jsx": "0.
|
|
72
|
-
"@barefootjs/vite": "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}` ``)
|
|
4705
|
-
//
|
|
4706
|
-
//
|
|
4707
|
-
//
|
|
4708
|
-
|
|
4859
|
+
// `` `#${row.id} ${row.label}` ``) folds through the shared value-operand
|
|
4860
|
+
// door (#2863) — a left-folded `bf_concat_str` chain — instead 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(
|
|
4887
|
+
expect(result.errors ?? []).toEqual([])
|
|
4730
4888
|
const template = result.files.find(f => f.type === 'markedTemplate')!.content
|
|
4731
|
-
expect(template).
|
|
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
|