@barefootjs/go-template 0.18.4 → 0.18.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/analysis/static-child-loop-bake.d.ts +61 -0
- package/dist/adapter/analysis/static-child-loop-bake.d.ts.map +1 -0
- package/dist/adapter/analysis/static-element-loop-bake.d.ts +83 -0
- package/dist/adapter/analysis/static-element-loop-bake.d.ts.map +1 -0
- package/dist/adapter/go-template-adapter.d.ts +151 -3
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +500 -52
- package/dist/adapter/lib/compile-state.d.ts +15 -0
- package/dist/adapter/lib/compile-state.d.ts.map +1 -1
- package/dist/adapter/lib/constants.d.ts.map +1 -1
- package/dist/adapter/memo/memo-compute.d.ts.map +1 -1
- package/dist/adapter/props/prop-classes.d.ts +40 -0
- package/dist/adapter/props/prop-classes.d.ts.map +1 -0
- package/dist/adapter/props/prop-types.d.ts.map +1 -1
- package/dist/adapter/type/type-codegen.d.ts +5 -1
- package/dist/adapter/type/type-codegen.d.ts.map +1 -1
- package/dist/adapter/value/value-lowering.d.ts.map +1 -1
- package/dist/build.js +500 -52
- package/dist/conformance-pins.d.ts.map +1 -1
- package/dist/index.js +503 -79
- package/dist/render-divergences.d.ts.map +1 -1
- package/dist/test-render.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/go-template-adapter.test.ts +708 -4
- package/src/adapter/analysis/static-child-loop-bake.ts +119 -0
- package/src/adapter/analysis/static-element-loop-bake.ts +211 -0
- package/src/adapter/go-template-adapter.ts +661 -34
- package/src/adapter/lib/compile-state.ts +17 -0
- package/src/adapter/lib/constants.ts +1 -0
- package/src/adapter/memo/memo-compute.ts +37 -9
- package/src/adapter/props/prop-classes.ts +70 -0
- package/src/adapter/props/prop-types.ts +69 -1
- package/src/adapter/type/type-codegen.ts +19 -2
- package/src/adapter/value/value-lowering.ts +27 -2
- package/src/conformance-pins.ts +30 -36
- package/src/render-divergences.ts +12 -30
- package/src/test-render.ts +131 -13
package/dist/adapter/index.js
CHANGED
|
@@ -25,7 +25,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
25
25
|
import {
|
|
26
26
|
BaseAdapter,
|
|
27
27
|
isBooleanAttr,
|
|
28
|
-
parseExpression as
|
|
28
|
+
parseExpression as parseExpression4,
|
|
29
29
|
stringifyParsedExpr as stringifyParsedExpr2,
|
|
30
30
|
parseStyleObjectEntries,
|
|
31
31
|
isSupported,
|
|
@@ -42,10 +42,17 @@ import {
|
|
|
42
42
|
collectModuleStringConsts as collectModuleStringConstsShared,
|
|
43
43
|
prepareLoweringMatchers,
|
|
44
44
|
envSignalReaderFor,
|
|
45
|
-
computeSsrSeedPlan
|
|
45
|
+
computeSsrSeedPlan,
|
|
46
|
+
isStringConcatBinary,
|
|
47
|
+
isDangerousInnerHtmlAttr,
|
|
48
|
+
resolveDangerousInnerHtml,
|
|
49
|
+
dangerousInnerHtmlMetacharViolation,
|
|
50
|
+
dangerousInnerHtmlDiagnostic,
|
|
51
|
+
collectLoopBoundNames as collectLoopBoundNames2,
|
|
52
|
+
evaluateStaticLiteral as evaluateStaticLiteral3
|
|
46
53
|
} from "@barefootjs/jsx";
|
|
47
54
|
import { findInterpolationEnd } from "@barefootjs/jsx/scanner";
|
|
48
|
-
import { BF_REGION } from "@barefootjs/shared";
|
|
55
|
+
import { BF_REGION, escapeHtml } from "@barefootjs/shared";
|
|
49
56
|
|
|
50
57
|
// src/adapter/lib/go-naming.ts
|
|
51
58
|
var GO_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -332,6 +339,7 @@ var GO_TEMPLATE_PRIMITIVES = {
|
|
|
332
339
|
"Math.floor": { arity: 1, emit: (args) => `bf_floor ${wrapGoArg(args[0])}` },
|
|
333
340
|
"Math.ceil": { arity: 1, emit: (args) => `bf_ceil ${wrapGoArg(args[0])}` },
|
|
334
341
|
"Math.round": { arity: 1, emit: (args) => `bf_round ${wrapGoArg(args[0])}` },
|
|
342
|
+
"Math.abs": { arity: 1, emit: (args) => `bf_abs ${wrapGoArg(args[0])}` },
|
|
335
343
|
"Math.min": { arity: 2, emit: (args) => `bf_min ${wrapGoArg(args[0])} ${wrapGoArg(args[1])}` },
|
|
336
344
|
"Math.max": { arity: 2, emit: (args) => `bf_max ${wrapGoArg(args[0])} ${wrapGoArg(args[1])}` }
|
|
337
345
|
};
|
|
@@ -347,6 +355,7 @@ class CompileState {
|
|
|
347
355
|
restPropsName = null;
|
|
348
356
|
moduleStringConsts = new Map;
|
|
349
357
|
localConstants = [];
|
|
358
|
+
staticLoopSourceBoundNames = new Set;
|
|
350
359
|
localHelperNames = new Set;
|
|
351
360
|
currentMemos = [];
|
|
352
361
|
currentTypeDefinitions = [];
|
|
@@ -357,6 +366,7 @@ class CompileState {
|
|
|
357
366
|
hoistedMemoLocals = new Map;
|
|
358
367
|
loweringMatchers = [];
|
|
359
368
|
nillablePropNames = new Set;
|
|
369
|
+
stringValueNames = new Set;
|
|
360
370
|
rootScopeNodes = new Set;
|
|
361
371
|
memoBackedLoopSlice = new Map;
|
|
362
372
|
usesHtmlTemplate = false;
|
|
@@ -514,6 +524,178 @@ function collectNestedComponents(node, result) {
|
|
|
514
524
|
}
|
|
515
525
|
}
|
|
516
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
|
+
|
|
517
699
|
// src/adapter/expr/helper-inline.ts
|
|
518
700
|
function inlineLocalHelperCall(ctx, jsExpr, callParsed) {
|
|
519
701
|
if (ctx.state.localHelperNames.size === 0)
|
|
@@ -701,7 +883,7 @@ function forEachValueChild(n, visit) {
|
|
|
701
883
|
|
|
702
884
|
// src/adapter/expr/url-builder.ts
|
|
703
885
|
import {
|
|
704
|
-
parseExpression,
|
|
886
|
+
parseExpression as parseExpression2,
|
|
705
887
|
stringifyParsedExpr,
|
|
706
888
|
isValidHelperId
|
|
707
889
|
} from "@barefootjs/jsx";
|
|
@@ -734,7 +916,7 @@ function lowerRegisteredCall(ctx, jsExpr, preParsed) {
|
|
|
734
916
|
if (!call) {
|
|
735
917
|
if (!/^\s*[A-Za-z_$][\w$]*\s*\(/.test(jsExpr))
|
|
736
918
|
return null;
|
|
737
|
-
const parsed =
|
|
919
|
+
const parsed = parseExpression2(jsExpr);
|
|
738
920
|
if (parsed.kind !== "call")
|
|
739
921
|
return null;
|
|
740
922
|
call = parsed;
|
|
@@ -776,7 +958,7 @@ function typeInfoToGo(ctx, typeInfo, defaultValue) {
|
|
|
776
958
|
case "string":
|
|
777
959
|
return "string";
|
|
778
960
|
case "number":
|
|
779
|
-
return "int";
|
|
961
|
+
return defaultValue !== undefined ? numberPrimitiveGoType(defaultValue) : "int";
|
|
780
962
|
case "boolean":
|
|
781
963
|
return "bool";
|
|
782
964
|
default:
|
|
@@ -827,6 +1009,9 @@ function tsTypeStringToGo(ctx, tsType) {
|
|
|
827
1009
|
return t;
|
|
828
1010
|
return "interface{}";
|
|
829
1011
|
}
|
|
1012
|
+
function numberPrimitiveGoType(value) {
|
|
1013
|
+
return /^-?\d+\.\d+$/.test(value) ? "float64" : "int";
|
|
1014
|
+
}
|
|
830
1015
|
function inferTypeFromValue(value) {
|
|
831
1016
|
if (value === "true" || value === "false")
|
|
832
1017
|
return "bool";
|
|
@@ -939,9 +1124,9 @@ function convertInitialValue(ctx, value, typeInfo, propsParams, preParsed) {
|
|
|
939
1124
|
return value === "true" ? "true" : "false";
|
|
940
1125
|
}
|
|
941
1126
|
if (typeInfo.primitive === "number") {
|
|
942
|
-
if (
|
|
1127
|
+
if (/^-?\d+$/.test(value))
|
|
943
1128
|
return value;
|
|
944
|
-
if (
|
|
1129
|
+
if (/^-?\d+\.\d+$/.test(value))
|
|
945
1130
|
return value;
|
|
946
1131
|
return "0";
|
|
947
1132
|
}
|
|
@@ -966,6 +1151,12 @@ function convertInitialValue(ctx, value, typeInfo, propsParams, preParsed) {
|
|
|
966
1151
|
}
|
|
967
1152
|
return '""';
|
|
968
1153
|
}
|
|
1154
|
+
if (ctx.state.localStructFields.has(typeInfo.raw)) {
|
|
1155
|
+
const baked = jsLiteralToGo(ctx, typeInfo, preParsed);
|
|
1156
|
+
if (baked !== null)
|
|
1157
|
+
return baked;
|
|
1158
|
+
return `${typeInfo.raw}{}`;
|
|
1159
|
+
}
|
|
969
1160
|
}
|
|
970
1161
|
return "nil";
|
|
971
1162
|
}
|
|
@@ -1656,10 +1847,11 @@ function memoInitialFromParsedBody(ctx, body, signals, propsParams, propFallback
|
|
|
1656
1847
|
const operand = String(body.right.value);
|
|
1657
1848
|
const depName = getterCallName(body.left);
|
|
1658
1849
|
if (depName) {
|
|
1659
|
-
const
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1850
|
+
const depInitial = resolveGetterValueAsGo(ctx, depName, signals, propsParams, propFallbackVars, resolving);
|
|
1851
|
+
const isArithmeticSafe = depInitial !== null && !depInitial.startsWith("func(");
|
|
1852
|
+
if (isArithmeticSafe) {
|
|
1853
|
+
const wrapped = /\s/.test(depInitial) ? `(${depInitial})` : depInitial;
|
|
1854
|
+
return `${wrapped} ${operator} ${operand}`;
|
|
1663
1855
|
}
|
|
1664
1856
|
}
|
|
1665
1857
|
const propName = propsMemberName(body.left);
|
|
@@ -1694,9 +1886,9 @@ function memoInitialFromParsedBody(ctx, body, signals, propsParams, propFallback
|
|
|
1694
1886
|
}
|
|
1695
1887
|
const simpleDep = getterCallName(body);
|
|
1696
1888
|
if (simpleDep) {
|
|
1697
|
-
const
|
|
1698
|
-
if (
|
|
1699
|
-
return
|
|
1889
|
+
const depInitial = resolveGetterValueAsGo(ctx, simpleDep, signals, propsParams, propFallbackVars, resolving);
|
|
1890
|
+
if (depInitial !== null) {
|
|
1891
|
+
return depInitial;
|
|
1700
1892
|
}
|
|
1701
1893
|
}
|
|
1702
1894
|
const simpleProp = propsMemberName(body);
|
|
@@ -1818,7 +2010,7 @@ function propsAccessNameFromParsed2(ctx, node) {
|
|
|
1818
2010
|
|
|
1819
2011
|
// src/adapter/spread/spread-codegen.ts
|
|
1820
2012
|
import ts2 from "typescript";
|
|
1821
|
-
import { parseExpression as
|
|
2013
|
+
import { parseExpression as parseExpression3, parseRecordIndexAccess } from "@barefootjs/jsx";
|
|
1822
2014
|
function collectSpreadSlots(ctx, node) {
|
|
1823
2015
|
const result = [];
|
|
1824
2016
|
collectSpreadSlotsRecursive(ctx, node, result);
|
|
@@ -1925,7 +2117,7 @@ function parsedObjectLiteralToGoMap(parsed) {
|
|
|
1925
2117
|
}
|
|
1926
2118
|
function buildSpreadInitializer(ctx, spreadExpr, ir, parsed) {
|
|
1927
2119
|
const trimmed = spreadExpr.trim();
|
|
1928
|
-
const conditionalTree = parsed ??
|
|
2120
|
+
const conditionalTree = parsed ?? parseExpression3(trimmed);
|
|
1929
2121
|
const conditional = buildConditionalSpreadInitializer(ctx, conditionalTree, ir);
|
|
1930
2122
|
if (conditional !== undefined)
|
|
1931
2123
|
return conditional;
|
|
@@ -1956,7 +2148,7 @@ function buildSpreadInitializer(ctx, spreadExpr, ir, parsed) {
|
|
|
1956
2148
|
if (localConst?.value !== undefined) {
|
|
1957
2149
|
const initTrimmed = localConst.value.trim();
|
|
1958
2150
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(initTrimmed)) {
|
|
1959
|
-
const resolved = buildConditionalSpreadInitializer(ctx,
|
|
2151
|
+
const resolved = buildConditionalSpreadInitializer(ctx, parseExpression3(initTrimmed), ir);
|
|
1960
2152
|
if (resolved)
|
|
1961
2153
|
return resolved;
|
|
1962
2154
|
if (resolved === null)
|
|
@@ -2087,8 +2279,47 @@ function buildPropTypeOverrides(ctx, ir) {
|
|
|
2087
2279
|
}
|
|
2088
2280
|
}
|
|
2089
2281
|
}
|
|
2282
|
+
for (const propName of collectToFixedPropNames(ir.root)) {
|
|
2283
|
+
const param = ir.metadata.propsParams.find((p) => p.name === propName);
|
|
2284
|
+
if (!param)
|
|
2285
|
+
continue;
|
|
2286
|
+
const resolved = overrides.get(propName) ?? typeInfoToGo(ctx, param.type, param.defaultValue);
|
|
2287
|
+
if (resolved === "int") {
|
|
2288
|
+
overrides.set(propName, "float64");
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2090
2291
|
return overrides;
|
|
2091
2292
|
}
|
|
2293
|
+
function collectToFixedPropNames(root) {
|
|
2294
|
+
const names = new Set;
|
|
2295
|
+
const checkExpr = (expr) => {
|
|
2296
|
+
if (expr?.kind === "array-method" && expr.method === "toFixed" && expr.object.kind === "identifier") {
|
|
2297
|
+
names.add(expr.object.name);
|
|
2298
|
+
}
|
|
2299
|
+
};
|
|
2300
|
+
const walk = (node) => {
|
|
2301
|
+
if (!node)
|
|
2302
|
+
return;
|
|
2303
|
+
if (node.type === "expression")
|
|
2304
|
+
checkExpr(node.parsed);
|
|
2305
|
+
if (node.type === "conditional") {
|
|
2306
|
+
checkExpr(node.parsedCondition);
|
|
2307
|
+
walk(node.whenTrue);
|
|
2308
|
+
walk(node.whenFalse);
|
|
2309
|
+
}
|
|
2310
|
+
if (node.type === "element") {
|
|
2311
|
+
for (const attr of node.attrs) {
|
|
2312
|
+
if (attr.value.kind === "expression")
|
|
2313
|
+
checkExpr(attr.value.parsed);
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
if ("children" in node && Array.isArray(node.children)) {
|
|
2317
|
+
node.children.forEach(walk);
|
|
2318
|
+
}
|
|
2319
|
+
};
|
|
2320
|
+
walk(root);
|
|
2321
|
+
return names;
|
|
2322
|
+
}
|
|
2092
2323
|
function resolvePropGoType(ctx, param, propTypeOverrides) {
|
|
2093
2324
|
const base = propTypeOverrides.get(param.name) ?? typeInfoToGo(ctx, param.type, param.defaultValue);
|
|
2094
2325
|
if (param.optional && ctx.state.localStructFields.has(base)) {
|
|
@@ -2107,6 +2338,38 @@ function collectNillablePropNames(ctx, ir) {
|
|
|
2107
2338
|
return nillable;
|
|
2108
2339
|
}
|
|
2109
2340
|
|
|
2341
|
+
// src/adapter/props/prop-classes.ts
|
|
2342
|
+
import { collectLoopBoundNames } from "@barefootjs/jsx";
|
|
2343
|
+
function isStringTypeInfo(type) {
|
|
2344
|
+
return type.kind === "primitive" && type.primitive === "string";
|
|
2345
|
+
}
|
|
2346
|
+
function isBareStringLiteral(initialValue) {
|
|
2347
|
+
if (!initialValue)
|
|
2348
|
+
return false;
|
|
2349
|
+
const v = initialValue.trim();
|
|
2350
|
+
return v.startsWith("'") && v.endsWith("'") || v.startsWith('"') && v.endsWith('"');
|
|
2351
|
+
}
|
|
2352
|
+
function collectStringValueNames(ir) {
|
|
2353
|
+
const names = new Set;
|
|
2354
|
+
for (const s of ir.metadata.signals) {
|
|
2355
|
+
if (isStringTypeInfo(s.type) || isBareStringLiteral(s.initialValue)) {
|
|
2356
|
+
names.add(s.getter);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
for (const p of ir.metadata.propsParams) {
|
|
2360
|
+
if (isStringTypeInfo(p.type))
|
|
2361
|
+
names.add(p.name);
|
|
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);
|
|
2370
|
+
return names;
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2110
2373
|
// src/adapter/go-template-adapter.ts
|
|
2111
2374
|
var STRING_METHODS = new Set([
|
|
2112
2375
|
"replace",
|
|
@@ -2148,12 +2411,16 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2148
2411
|
return this.state.errors;
|
|
2149
2412
|
}
|
|
2150
2413
|
inLoop = false;
|
|
2414
|
+
bakedStaticChildLoopCache = new Map;
|
|
2151
2415
|
loopParamStack = [];
|
|
2416
|
+
loopKeyDepthStack = [];
|
|
2152
2417
|
loopScalarItemStack = [];
|
|
2153
2418
|
loopWrapperStack = [];
|
|
2154
2419
|
loopVarRefCount = new Map;
|
|
2155
2420
|
loopBindingStack = [];
|
|
2156
2421
|
loopRestExcludeStack = [];
|
|
2422
|
+
staticLoopItemStack = [];
|
|
2423
|
+
staticLoopBakeFailed = false;
|
|
2157
2424
|
childComponentShapes = new Map;
|
|
2158
2425
|
childContextConsumers = new Map;
|
|
2159
2426
|
constructor(options = {}) {
|
|
@@ -2169,6 +2436,8 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2169
2436
|
this.state.restPropsName = ir.metadata.restPropsName ?? null;
|
|
2170
2437
|
this.state.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
|
|
2171
2438
|
this.state.localConstants = ir.metadata.localConstants ?? [];
|
|
2439
|
+
this.state.staticLoopSourceBoundNames = collectLoopBoundNames2(ir);
|
|
2440
|
+
this.bakedStaticChildLoopCache = new Map;
|
|
2172
2441
|
this.state.localHelperNames = new Set(this.state.localConstants.filter((c) => !c.isModule && c.containsArrow).map((c) => c.name));
|
|
2173
2442
|
this.state.currentMemos = ir.metadata.memos ?? [];
|
|
2174
2443
|
this.state.currentTypeDefinitions = ir.metadata.typeDefinitions ?? [];
|
|
@@ -2196,6 +2465,7 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2196
2465
|
this.state.pendingChildrenDefines = [];
|
|
2197
2466
|
this.primeCompileState(ir);
|
|
2198
2467
|
this.state.nillablePropNames = collectNillablePropNames(this.emitCtx, ir);
|
|
2468
|
+
this.state.stringValueNames = collectStringValueNames(ir);
|
|
2199
2469
|
if (!options?.siblingTemplatesRegistered) {
|
|
2200
2470
|
this.checkImportedLoopChildComponents(ir);
|
|
2201
2471
|
}
|
|
@@ -2549,6 +2819,8 @@ ${goFields.join(`
|
|
|
2549
2819
|
lines.push(` ${fieldName} ${goType}`);
|
|
2550
2820
|
}
|
|
2551
2821
|
for (const nested of inputNested) {
|
|
2822
|
+
if (nested.loopMarkerId && this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey))
|
|
2823
|
+
continue;
|
|
2552
2824
|
lines.push(` ${nested.name}s []${nested.name}Input`);
|
|
2553
2825
|
}
|
|
2554
2826
|
const takenInput = new Set(ir.metadata.propsParams.map((p) => capitalizeFieldName(p.name)));
|
|
@@ -2680,6 +2952,21 @@ ${goFields.join(`
|
|
|
2680
2952
|
const staticWithoutBody = staticNested.filter((n) => !n.bodyChildren || n.bodyChildren.length === 0);
|
|
2681
2953
|
for (const nested of staticWithoutBody) {
|
|
2682
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
|
+
}
|
|
2683
2970
|
lines.push(` ${varName} := make([]${nested.name}Props, len(in.${nested.name}s))`);
|
|
2684
2971
|
lines.push(` for i, item := range in.${nested.name}s {`);
|
|
2685
2972
|
lines.push(` ${varName}[i] = New${nested.name}Props(item)`);
|
|
@@ -2893,6 +3180,13 @@ ${goFields.join(`
|
|
|
2893
3180
|
break;
|
|
2894
3181
|
}
|
|
2895
3182
|
}
|
|
3183
|
+
if (parsedValue) {
|
|
3184
|
+
const goVal = parsedLiteralToGo(this.emitCtx, parsedValue);
|
|
3185
|
+
if (goVal !== null) {
|
|
3186
|
+
emitChildField(prop.name, goVal);
|
|
3187
|
+
break;
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
2896
3190
|
const resolvedValue = this.resolveDynamicPropValue(exprText, ir.metadata.signals, ir.metadata.memos, ir.metadata.propsParams);
|
|
2897
3191
|
if (resolvedValue !== null) {
|
|
2898
3192
|
emitChildField(prop.name, resolvedValue);
|
|
@@ -2900,6 +3194,24 @@ ${goFields.join(`
|
|
|
2900
3194
|
break;
|
|
2901
3195
|
}
|
|
2902
3196
|
case "jsx-children":
|
|
3197
|
+
if (prop.name !== "children") {
|
|
3198
|
+
const text = this.extractTextChildren(prop.value.children);
|
|
3199
|
+
if (text !== null) {
|
|
3200
|
+
emitChildField(prop.name, JSON.stringify(text));
|
|
3201
|
+
} else {
|
|
3202
|
+
const html = this.extractHtmlChildren(prop.value.children);
|
|
3203
|
+
if (html !== null) {
|
|
3204
|
+
this.state.usesHtmlTemplate = true;
|
|
3205
|
+
emitChildField(prop.name, `template.HTML(${JSON.stringify(html)})`);
|
|
3206
|
+
} else {
|
|
3207
|
+
const scopedHtml = this.extractScopedHtmlChildren(prop.value.children);
|
|
3208
|
+
if (scopedHtml !== null) {
|
|
3209
|
+
this.state.usesHtmlTemplate = true;
|
|
3210
|
+
emitChildField(prop.name, `template.HTML(${scopedHtml})`);
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
2903
3215
|
break;
|
|
2904
3216
|
}
|
|
2905
3217
|
}
|
|
@@ -3541,6 +3853,17 @@ ${goFields.join(`
|
|
|
3541
3853
|
return computeMemoInitialValueOrNull(this.emitCtx, memo, signals, propsParams, undefined, new Set([memo.name]));
|
|
3542
3854
|
}
|
|
3543
3855
|
}
|
|
3856
|
+
const propsObjectName = this.state.propsObjectName;
|
|
3857
|
+
const identifierPattern = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
3858
|
+
const bareIdentifier = identifierPattern.test(expr) ? expr : null;
|
|
3859
|
+
const barePropAccess = propsObjectName && expr.startsWith(`${propsObjectName}.`) && identifierPattern.test(expr.slice(propsObjectName.length + 1)) ? expr.slice(propsObjectName.length + 1) : null;
|
|
3860
|
+
const passthroughName = bareIdentifier ?? barePropAccess;
|
|
3861
|
+
const localConst = this.state.localConstants.find((c) => c.name === passthroughName);
|
|
3862
|
+
const isPropsDestructureAlias = localConst !== undefined && propsObjectName !== null && localConst.value === `${propsObjectName}.${passthroughName}`;
|
|
3863
|
+
const shadowedByLocal = passthroughName !== null && (localConst !== undefined && !isPropsDestructureAlias || this.state.localHelperNames.has(passthroughName));
|
|
3864
|
+
if (passthroughName && !shadowedByLocal && propsParams.some((p) => p.name === passthroughName)) {
|
|
3865
|
+
return `in.${capitalizeFieldName(passthroughName)}`;
|
|
3866
|
+
}
|
|
3544
3867
|
return null;
|
|
3545
3868
|
}
|
|
3546
3869
|
inferMemoType(memo, signals, propsParamMap) {
|
|
@@ -3665,7 +3988,7 @@ ${goFields.join(`
|
|
|
3665
3988
|
return this.renderElement(node);
|
|
3666
3989
|
}
|
|
3667
3990
|
emitText(node) {
|
|
3668
|
-
return node.value;
|
|
3991
|
+
return escapeHtml(node.value);
|
|
3669
3992
|
}
|
|
3670
3993
|
emitExpression(node) {
|
|
3671
3994
|
return this.renderExpression(node);
|
|
@@ -3697,7 +4020,8 @@ ${goFields.join(`
|
|
|
3697
4020
|
renderElement(element) {
|
|
3698
4021
|
const tag = element.tag;
|
|
3699
4022
|
const attrs = this.renderAttributes(element);
|
|
3700
|
-
const
|
|
4023
|
+
const dangerousHtml = this.renderDangerousInnerHtml(element);
|
|
4024
|
+
const children = dangerousHtml !== null ? dangerousHtml : this.renderChildren(element.children);
|
|
3701
4025
|
let hydrationAttrs = "";
|
|
3702
4026
|
if (element.needsScope) {
|
|
3703
4027
|
hydrationAttrs += ` ${this.renderScopeMarker(".ScopeID")}`;
|
|
@@ -3732,6 +4056,22 @@ ${goFields.join(`
|
|
|
3732
4056
|
}
|
|
3733
4057
|
return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
|
|
3734
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
|
+
}
|
|
3735
4075
|
renderExpression(expr) {
|
|
3736
4076
|
if (expr.clientOnly) {
|
|
3737
4077
|
if (expr.slotId) {
|
|
@@ -3925,7 +4265,7 @@ ${goFields.join(`
|
|
|
3925
4265
|
const argsStr = args.map(emit).join(" ");
|
|
3926
4266
|
return `${calleeStr} ${argsStr}`;
|
|
3927
4267
|
}
|
|
3928
|
-
member(object, property, _computed, emit) {
|
|
4268
|
+
member(object, property, _computed, optional, emit) {
|
|
3929
4269
|
const objHO = this.higherOrderShapeOf(object);
|
|
3930
4270
|
if (property === "length" && objHO) {
|
|
3931
4271
|
const result = this.renderFilterLengthExpr(objHO, emit);
|
|
@@ -3967,6 +4307,9 @@ ${goFields.join(`
|
|
|
3967
4307
|
const obj = emit(object);
|
|
3968
4308
|
if (property === "length")
|
|
3969
4309
|
return `len ${obj}`;
|
|
4310
|
+
if (optional) {
|
|
4311
|
+
return `bf_get ${wrapIfMultiToken(obj)} ${JSON.stringify(goFieldNameForKey(property))}`;
|
|
4312
|
+
}
|
|
3970
4313
|
return `${obj}.${goFieldNameForKey(property)}`;
|
|
3971
4314
|
}
|
|
3972
4315
|
indexAccess(object, index, emit) {
|
|
@@ -3997,7 +4340,7 @@ ${goFields.join(`
|
|
|
3997
4340
|
case "<=":
|
|
3998
4341
|
return `le ${wl} ${wr}`;
|
|
3999
4342
|
case "+":
|
|
4000
|
-
return
|
|
4343
|
+
return this._emitPlus(left, right, wl, wr);
|
|
4001
4344
|
case "-":
|
|
4002
4345
|
return `bf_sub ${wl} ${wr}`;
|
|
4003
4346
|
case "*":
|
|
@@ -4010,6 +4353,15 @@ ${goFields.join(`
|
|
|
4010
4353
|
return `${l} ${op} ${r}`;
|
|
4011
4354
|
}
|
|
4012
4355
|
}
|
|
4356
|
+
_emitPlus(leftExpr, rightExpr, leftRendered, rightRendered) {
|
|
4357
|
+
if (isStringConcatBinary("+", leftExpr, rightExpr, (n) => this._isStringValueName(n))) {
|
|
4358
|
+
return `bf_concat_str ${leftRendered} ${rightRendered}`;
|
|
4359
|
+
}
|
|
4360
|
+
return `bf_add ${leftRendered} ${rightRendered}`;
|
|
4361
|
+
}
|
|
4362
|
+
_isStringValueName(name) {
|
|
4363
|
+
return this.state.stringValueNames.has(name);
|
|
4364
|
+
}
|
|
4013
4365
|
unary(op, argument, emit) {
|
|
4014
4366
|
const arg = emit(argument);
|
|
4015
4367
|
if (op === "!")
|
|
@@ -4221,6 +4573,12 @@ ${goFields.join(`
|
|
|
4221
4573
|
const recv = emit(object);
|
|
4222
4574
|
return `bf_trim ${wrapIfMultiToken(recv)}`;
|
|
4223
4575
|
}
|
|
4576
|
+
case "trimStart":
|
|
4577
|
+
case "trimEnd": {
|
|
4578
|
+
const fn = method === "trimStart" ? "bf_trim_start" : "bf_trim_end";
|
|
4579
|
+
const recv = emit(object);
|
|
4580
|
+
return `${fn} ${wrapIfMultiToken(recv)}`;
|
|
4581
|
+
}
|
|
4224
4582
|
case "toFixed": {
|
|
4225
4583
|
const recv = emit(object);
|
|
4226
4584
|
const digits = args.length >= 1 ? emit(args[0]) : "0";
|
|
@@ -4255,6 +4613,12 @@ ${goFields.join(`
|
|
|
4255
4613
|
const newS = emit(args[1]);
|
|
4256
4614
|
return `bf_replace ${wrapIfMultiToken(recv)} ${wrapIfMultiToken(oldS)} ${wrapIfMultiToken(newS)}`;
|
|
4257
4615
|
}
|
|
4616
|
+
case "replaceAll": {
|
|
4617
|
+
const recv = emit(object);
|
|
4618
|
+
const oldS = emit(args[0]);
|
|
4619
|
+
const newS = emit(args[1]);
|
|
4620
|
+
return `bf_replace_all ${wrapIfMultiToken(recv)} ${wrapIfMultiToken(oldS)} ${wrapIfMultiToken(newS)}`;
|
|
4621
|
+
}
|
|
4258
4622
|
case "repeat": {
|
|
4259
4623
|
const recv = emit(object);
|
|
4260
4624
|
const count = args.length === 0 ? "0" : emit(args[0]);
|
|
@@ -4441,8 +4805,8 @@ ${goFields.join(`
|
|
|
4441
4805
|
const value = negated ? "false" : "true";
|
|
4442
4806
|
return `len (bf_filter ${arrayExpr} "${field}" ${value})`;
|
|
4443
4807
|
}
|
|
4444
|
-
renderPredicateCondition(pred, param) {
|
|
4445
|
-
return this.renderFilterExpr(pred, param);
|
|
4808
|
+
renderPredicateCondition(pred, param, datumField) {
|
|
4809
|
+
return this.renderFilterExpr(pred, param, new Map, datumField ?? undefined);
|
|
4446
4810
|
}
|
|
4447
4811
|
needsParens(expr) {
|
|
4448
4812
|
return expr.kind === "logical" || expr.kind === "unary" || expr.kind === "conditional";
|
|
@@ -4463,21 +4827,23 @@ ${goFields.join(`
|
|
|
4463
4827
|
}
|
|
4464
4828
|
return null;
|
|
4465
4829
|
}
|
|
4466
|
-
renderFilterExpr(expr, param, localVarMap = new Map) {
|
|
4830
|
+
renderFilterExpr(expr, param, localVarMap = new Map, datumField) {
|
|
4467
4831
|
if (this.filterExprDepth === 0)
|
|
4468
4832
|
this.filterExprUnsupported = false;
|
|
4469
4833
|
this.filterExprDepth++;
|
|
4470
4834
|
try {
|
|
4471
|
-
return this.renderFilterExprNode(expr, param, localVarMap);
|
|
4835
|
+
return this.renderFilterExprNode(expr, param, localVarMap, datumField);
|
|
4472
4836
|
} finally {
|
|
4473
4837
|
this.filterExprDepth--;
|
|
4474
4838
|
}
|
|
4475
4839
|
}
|
|
4476
|
-
renderFilterExprNode(expr, param, localVarMap) {
|
|
4840
|
+
renderFilterExprNode(expr, param, localVarMap, datumField) {
|
|
4841
|
+
const paramPrefix = datumField ? `.${datumField}` : "";
|
|
4842
|
+
const paramDot = paramPrefix || ".";
|
|
4477
4843
|
switch (expr.kind) {
|
|
4478
4844
|
case "identifier": {
|
|
4479
4845
|
if (expr.name === param) {
|
|
4480
|
-
return
|
|
4846
|
+
return paramDot;
|
|
4481
4847
|
}
|
|
4482
4848
|
const signal = localVarMap.get(expr.name);
|
|
4483
4849
|
if (signal) {
|
|
@@ -4495,24 +4861,24 @@ ${goFields.join(`
|
|
|
4495
4861
|
return String(expr.value);
|
|
4496
4862
|
case "member": {
|
|
4497
4863
|
if (expr.object.kind === "identifier" && expr.object.name === param) {
|
|
4498
|
-
return
|
|
4864
|
+
return `${paramPrefix}.${capitalizeFieldName(expr.property)}`;
|
|
4499
4865
|
}
|
|
4500
4866
|
if (expr.property === "length") {
|
|
4501
4867
|
const innerHO = this.higherOrderShapeOf(expr.object);
|
|
4502
4868
|
if (innerHO && innerHO.method === "filter") {
|
|
4503
|
-
const lenExpr = this.renderFilterLengthExpr(innerHO, (e) => this.renderFilterExpr(e, param, localVarMap));
|
|
4869
|
+
const lenExpr = this.renderFilterLengthExpr(innerHO, (e) => this.renderFilterExpr(e, param, localVarMap, datumField));
|
|
4504
4870
|
if (lenExpr)
|
|
4505
4871
|
return `(${lenExpr})`;
|
|
4506
4872
|
}
|
|
4507
4873
|
}
|
|
4508
|
-
const obj = this.renderFilterExpr(expr.object, param, localVarMap);
|
|
4874
|
+
const obj = this.renderFilterExpr(expr.object, param, localVarMap, datumField);
|
|
4509
4875
|
if (this.filterExprUnsupported)
|
|
4510
4876
|
return "false";
|
|
4511
4877
|
return `${obj}.${capitalizeFieldName(expr.property)}`;
|
|
4512
4878
|
}
|
|
4513
4879
|
case "call": {
|
|
4514
4880
|
if (expr.callee.kind === "member" && expr.callee.object.kind === "identifier" && expr.callee.object.name === param) {
|
|
4515
|
-
return
|
|
4881
|
+
return `${paramPrefix}.${capitalizeFieldName(expr.callee.property)}`;
|
|
4516
4882
|
}
|
|
4517
4883
|
if (expr.callee.kind === "identifier" && expr.args.length === 0) {
|
|
4518
4884
|
return `$.${capitalizeFieldName(expr.callee.name)}`;
|
|
@@ -4520,13 +4886,13 @@ ${goFields.join(`
|
|
|
4520
4886
|
if (asCallbackMethodCall3(expr) !== null) {
|
|
4521
4887
|
return this.refuseFilterExprNode(expr);
|
|
4522
4888
|
}
|
|
4523
|
-
const result = this.renderFilterExpr(expr.callee, param, localVarMap);
|
|
4889
|
+
const result = this.renderFilterExpr(expr.callee, param, localVarMap, datumField);
|
|
4524
4890
|
if (this.filterExprUnsupported)
|
|
4525
4891
|
return "false";
|
|
4526
4892
|
return result;
|
|
4527
4893
|
}
|
|
4528
4894
|
case "unary": {
|
|
4529
|
-
const arg = this.renderFilterExpr(expr.argument, param, localVarMap);
|
|
4895
|
+
const arg = this.renderFilterExpr(expr.argument, param, localVarMap, datumField);
|
|
4530
4896
|
if (this.filterExprUnsupported)
|
|
4531
4897
|
return "false";
|
|
4532
4898
|
if (expr.op === "!") {
|
|
@@ -4539,10 +4905,10 @@ ${goFields.join(`
|
|
|
4539
4905
|
return arg;
|
|
4540
4906
|
}
|
|
4541
4907
|
case "binary": {
|
|
4542
|
-
const left = this.renderFilterExpr(expr.left, param, localVarMap);
|
|
4908
|
+
const left = this.renderFilterExpr(expr.left, param, localVarMap, datumField);
|
|
4543
4909
|
if (this.filterExprUnsupported)
|
|
4544
4910
|
return "false";
|
|
4545
|
-
const right = this.renderFilterExpr(expr.right, param, localVarMap);
|
|
4911
|
+
const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField);
|
|
4546
4912
|
if (this.filterExprUnsupported)
|
|
4547
4913
|
return "false";
|
|
4548
4914
|
switch (expr.op) {
|
|
@@ -4561,7 +4927,7 @@ ${goFields.join(`
|
|
|
4561
4927
|
case "<=":
|
|
4562
4928
|
return `le ${left} ${right}`;
|
|
4563
4929
|
case "+":
|
|
4564
|
-
return
|
|
4930
|
+
return this._emitPlus(expr.left, expr.right, left, right);
|
|
4565
4931
|
case "-":
|
|
4566
4932
|
return `bf_sub ${left} ${right}`;
|
|
4567
4933
|
case "*":
|
|
@@ -4573,10 +4939,10 @@ ${goFields.join(`
|
|
|
4573
4939
|
}
|
|
4574
4940
|
}
|
|
4575
4941
|
case "logical": {
|
|
4576
|
-
const left = this.renderFilterExpr(expr.left, param, localVarMap);
|
|
4942
|
+
const left = this.renderFilterExpr(expr.left, param, localVarMap, datumField);
|
|
4577
4943
|
if (this.filterExprUnsupported)
|
|
4578
4944
|
return "false";
|
|
4579
|
-
const right = this.renderFilterExpr(expr.right, param, localVarMap);
|
|
4945
|
+
const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField);
|
|
4580
4946
|
if (this.filterExprUnsupported)
|
|
4581
4947
|
return "false";
|
|
4582
4948
|
if (expr.op === "&&") {
|
|
@@ -4644,11 +5010,22 @@ ${goFields.join(`
|
|
|
4644
5010
|
if (trimmed === "null" || trimmed === "undefined") {
|
|
4645
5011
|
return '""';
|
|
4646
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
|
+
}
|
|
4647
5024
|
const staticIndexed = this.resolveStaticRecordLiteralIndex(trimmed);
|
|
4648
5025
|
if (staticIndexed !== null) {
|
|
4649
5026
|
return staticIndexed;
|
|
4650
5027
|
}
|
|
4651
|
-
if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
|
|
5028
|
+
if (!this.isLoopShadowedName(trimmed) && /^[A-Za-z_$][\w$]*$/.test(trimmed)) {
|
|
4652
5029
|
const litConst = (this.state.localConstants ?? []).find((c) => c.name === trimmed);
|
|
4653
5030
|
if (litConst?.value !== undefined) {
|
|
4654
5031
|
const v = litConst.value.trim();
|
|
@@ -4666,7 +5043,7 @@ ${goFields.join(`
|
|
|
4666
5043
|
if (inlined !== null) {
|
|
4667
5044
|
return this.convertExpressionToGo(stringifyParsedExpr2(inlined), out, inlined);
|
|
4668
5045
|
}
|
|
4669
|
-
const parsed = preParsed ??
|
|
5046
|
+
const parsed = preParsed ?? parseExpression4(trimmed);
|
|
4670
5047
|
const support = isSupported(parsed);
|
|
4671
5048
|
if (!support.supported) {
|
|
4672
5049
|
this.state.errors.push({
|
|
@@ -4684,10 +5061,15 @@ ${goFields.join(`
|
|
|
4684
5061
|
out.parsed = parsed;
|
|
4685
5062
|
return this.renderParsedExpr(parsed);
|
|
4686
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
|
+
}
|
|
4687
5067
|
resolveStaticRecordLiteralIndex(jsExpr) {
|
|
4688
5068
|
const m = /^([A-Za-z_$][\w$]*)\[\s*(?:'([^']*)'|"([^"]*)")\s*\]$/.exec(jsExpr) ?? /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/.exec(jsExpr);
|
|
4689
5069
|
if (!m)
|
|
4690
5070
|
return null;
|
|
5071
|
+
if (this.isLoopShadowedName(m[1]))
|
|
5072
|
+
return null;
|
|
4691
5073
|
const key = m[2] ?? m[3];
|
|
4692
5074
|
const constInfo = (this.state.localConstants ?? []).find((c) => c.name === m[1] && c.isModule);
|
|
4693
5075
|
if (constInfo?.value === undefined)
|
|
@@ -4793,7 +5175,7 @@ ${goFields.join(`
|
|
|
4793
5175
|
}
|
|
4794
5176
|
convertConditionToGo(jsCondition, preParsed) {
|
|
4795
5177
|
const trimmed = jsCondition.trim();
|
|
4796
|
-
const parsed = preParsed ??
|
|
5178
|
+
const parsed = preParsed ?? parseExpression4(trimmed);
|
|
4797
5179
|
const support = isSupported(parsed);
|
|
4798
5180
|
if (!support.supported) {
|
|
4799
5181
|
this.state.errors.push({
|
|
@@ -4930,7 +5312,7 @@ ${goFields.join(`
|
|
|
4930
5312
|
result = `le ${left} ${right}`;
|
|
4931
5313
|
break;
|
|
4932
5314
|
case "+":
|
|
4933
|
-
result =
|
|
5315
|
+
result = this._emitPlus(expr.left, expr.right, left, right);
|
|
4934
5316
|
break;
|
|
4935
5317
|
case "-":
|
|
4936
5318
|
result = `bf_sub ${left} ${right}`;
|
|
@@ -5014,6 +5396,29 @@ ${goFields.join(`
|
|
|
5014
5396
|
}
|
|
5015
5397
|
return;
|
|
5016
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
|
+
}
|
|
5017
5422
|
renderLoop(loop) {
|
|
5018
5423
|
if (loop.clientOnly) {
|
|
5019
5424
|
return `{{bfComment "loop:${loop.markerId}"}}{{bfComment "/loop:${loop.markerId}"}}`;
|
|
@@ -5034,8 +5439,13 @@ ${goFields.join(`
|
|
|
5034
5439
|
}
|
|
5035
5440
|
});
|
|
5036
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
|
+
}
|
|
5037
5447
|
const arrayName = loop.array.trim();
|
|
5038
|
-
if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
|
|
5448
|
+
if (bakedChildLoop === null && /^[A-Za-z_$][\w$]*$/.test(arrayName)) {
|
|
5039
5449
|
const arrayConst = this.state.localConstants.find((c) => c.name === arrayName);
|
|
5040
5450
|
if (arrayConst && !arrayConst.isModule && arrayConst.parsed && !this.isStringExpr(arrayConst.parsed, new Set)) {
|
|
5041
5451
|
this.state.errors.push({
|
|
@@ -5049,7 +5459,7 @@ ${goFields.join(`
|
|
|
5049
5459
|
});
|
|
5050
5460
|
}
|
|
5051
5461
|
}
|
|
5052
|
-
let goArray = this.convertExpressionToGo(loop.array);
|
|
5462
|
+
let goArray = loop.childComponent ? "" : this.convertExpressionToGo(loop.array);
|
|
5053
5463
|
const param = loop.param;
|
|
5054
5464
|
let index = loop.index || "_";
|
|
5055
5465
|
let rangeIndex = index;
|
|
@@ -5087,7 +5497,9 @@ ${goFields.join(`
|
|
|
5087
5497
|
}
|
|
5088
5498
|
this.loopScalarItemStack.push(this.scalarLiteralLoopGoType(loop.arrayParsed, loop.itemType) !== null);
|
|
5089
5499
|
this.loopWrapperStack.push(!!loop.childComponent);
|
|
5500
|
+
this.loopKeyDepthStack.push(loop.depth);
|
|
5090
5501
|
const children = this.renderChildren(loop.children);
|
|
5502
|
+
this.loopKeyDepthStack.pop();
|
|
5091
5503
|
this.loopWrapperStack.pop();
|
|
5092
5504
|
this.loopScalarItemStack.pop();
|
|
5093
5505
|
const itemMarker = this.loopItemMarker(loop);
|
|
@@ -5115,7 +5527,8 @@ ${goFields.join(`
|
|
|
5115
5527
|
if (loop.filterPredicate) {
|
|
5116
5528
|
let filterCond;
|
|
5117
5529
|
if (loop.filterPredicate.predicate) {
|
|
5118
|
-
|
|
5530
|
+
const datumField = this.wrapperDatumField(loop);
|
|
5531
|
+
filterCond = this.renderPredicateCondition(loop.filterPredicate.predicate, loop.filterPredicate.param, datumField);
|
|
5119
5532
|
} else {
|
|
5120
5533
|
filterCond = "true";
|
|
5121
5534
|
}
|
|
@@ -5123,6 +5536,38 @@ ${goFields.join(`
|
|
|
5123
5536
|
}
|
|
5124
5537
|
return `{{bfComment "loop:${loop.markerId}"}}{{range $${rangeIndex}, $${rangeValue} := ${goArray}}}${itemMarker}${children}{{end}}{{bfComment "/loop:${loop.markerId}"}}`;
|
|
5125
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
|
+
}
|
|
5126
5571
|
loopItemMarker(loop) {
|
|
5127
5572
|
if (loop.bodyIsMultiRoot)
|
|
5128
5573
|
return `{{bfComment "bf-loop-i"}}`;
|
|
@@ -5230,7 +5675,7 @@ ${goFields.join(`
|
|
|
5230
5675
|
${children}`;
|
|
5231
5676
|
}
|
|
5232
5677
|
elementAttrEmitter = {
|
|
5233
|
-
emitLiteral: (value, name) => `${name}="${value.value}"`,
|
|
5678
|
+
emitLiteral: (value, name) => `${name}="${escapeHtml(value.value)}"`,
|
|
5234
5679
|
emitExpression: (value, name) => {
|
|
5235
5680
|
if (name === "style") {
|
|
5236
5681
|
const css = this.tryLowerStyleObject(value.expr);
|
|
@@ -5242,7 +5687,7 @@ ${children}`;
|
|
|
5242
5687
|
const body = name.startsWith("aria-") ? `${name}="true"` : name;
|
|
5243
5688
|
return `${preamble}{{if ${goCond}}}${body}{{end}}`;
|
|
5244
5689
|
}
|
|
5245
|
-
const parsed = value.parsed ??
|
|
5690
|
+
const parsed = value.parsed ?? parseExpression4(value.expr.trim());
|
|
5246
5691
|
if (parsed.kind === "conditional") {
|
|
5247
5692
|
const undef = (e) => e.kind === "identifier" && (e.name === "undefined" || e.name === "null") || e.kind === "literal" && (e.value === null || e.value === undefined);
|
|
5248
5693
|
const test = parsed.test;
|
|
@@ -5307,7 +5752,7 @@ ${children}`;
|
|
|
5307
5752
|
if (!entries)
|
|
5308
5753
|
return null;
|
|
5309
5754
|
for (const e of entries) {
|
|
5310
|
-
if (e.kind === "expr" && !isSupported(
|
|
5755
|
+
if (e.kind === "expr" && !isSupported(parseExpression4(e.expr)).supported)
|
|
5311
5756
|
return null;
|
|
5312
5757
|
}
|
|
5313
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(";");
|
|
@@ -5317,12 +5762,15 @@ ${children}`;
|
|
|
5317
5762
|
for (const attr of element.attrs) {
|
|
5318
5763
|
if (attr.clientOnly)
|
|
5319
5764
|
continue;
|
|
5765
|
+
if (isDangerousInnerHtmlAttr(attr))
|
|
5766
|
+
continue;
|
|
5320
5767
|
let attrName;
|
|
5321
5768
|
if (attr.name === "className")
|
|
5322
5769
|
attrName = "class";
|
|
5323
|
-
else if (attr.name === "key")
|
|
5324
|
-
|
|
5325
|
-
|
|
5770
|
+
else if (attr.name === "key") {
|
|
5771
|
+
const depth = this.loopKeyDepthStack.at(-1) ?? 0;
|
|
5772
|
+
attrName = depth > 0 ? `data-key-${depth}` : "data-key";
|
|
5773
|
+
} else
|
|
5326
5774
|
attrName = attr.name;
|
|
5327
5775
|
const lowered = emitAttrValue(attr.value, this.elementAttrEmitter, attrName);
|
|
5328
5776
|
if (lowered)
|