@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/build.js
CHANGED
|
@@ -365,7 +365,7 @@ var init_path = __esm(() => {
|
|
|
365
365
|
import {
|
|
366
366
|
BaseAdapter,
|
|
367
367
|
isBooleanAttr,
|
|
368
|
-
parseExpression as
|
|
368
|
+
parseExpression as parseExpression4,
|
|
369
369
|
stringifyParsedExpr as stringifyParsedExpr2,
|
|
370
370
|
parseStyleObjectEntries,
|
|
371
371
|
isSupported,
|
|
@@ -382,10 +382,17 @@ import {
|
|
|
382
382
|
collectModuleStringConsts as collectModuleStringConstsShared,
|
|
383
383
|
prepareLoweringMatchers,
|
|
384
384
|
envSignalReaderFor,
|
|
385
|
-
computeSsrSeedPlan
|
|
385
|
+
computeSsrSeedPlan,
|
|
386
|
+
isStringConcatBinary,
|
|
387
|
+
isDangerousInnerHtmlAttr,
|
|
388
|
+
resolveDangerousInnerHtml,
|
|
389
|
+
dangerousInnerHtmlMetacharViolation,
|
|
390
|
+
dangerousInnerHtmlDiagnostic,
|
|
391
|
+
collectLoopBoundNames as collectLoopBoundNames2,
|
|
392
|
+
evaluateStaticLiteral as evaluateStaticLiteral3
|
|
386
393
|
} from "@barefootjs/jsx";
|
|
387
394
|
import { findInterpolationEnd } from "@barefootjs/jsx/scanner";
|
|
388
|
-
import { BF_REGION } from "@barefootjs/shared";
|
|
395
|
+
import { BF_REGION, escapeHtml } from "@barefootjs/shared";
|
|
389
396
|
|
|
390
397
|
// src/adapter/lib/go-naming.ts
|
|
391
398
|
var GO_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -672,6 +679,7 @@ var GO_TEMPLATE_PRIMITIVES = {
|
|
|
672
679
|
"Math.floor": { arity: 1, emit: (args) => `bf_floor ${wrapGoArg(args[0])}` },
|
|
673
680
|
"Math.ceil": { arity: 1, emit: (args) => `bf_ceil ${wrapGoArg(args[0])}` },
|
|
674
681
|
"Math.round": { arity: 1, emit: (args) => `bf_round ${wrapGoArg(args[0])}` },
|
|
682
|
+
"Math.abs": { arity: 1, emit: (args) => `bf_abs ${wrapGoArg(args[0])}` },
|
|
675
683
|
"Math.min": { arity: 2, emit: (args) => `bf_min ${wrapGoArg(args[0])} ${wrapGoArg(args[1])}` },
|
|
676
684
|
"Math.max": { arity: 2, emit: (args) => `bf_max ${wrapGoArg(args[0])} ${wrapGoArg(args[1])}` }
|
|
677
685
|
};
|
|
@@ -687,6 +695,7 @@ class CompileState {
|
|
|
687
695
|
restPropsName = null;
|
|
688
696
|
moduleStringConsts = new Map;
|
|
689
697
|
localConstants = [];
|
|
698
|
+
staticLoopSourceBoundNames = new Set;
|
|
690
699
|
localHelperNames = new Set;
|
|
691
700
|
currentMemos = [];
|
|
692
701
|
currentTypeDefinitions = [];
|
|
@@ -697,6 +706,7 @@ class CompileState {
|
|
|
697
706
|
hoistedMemoLocals = new Map;
|
|
698
707
|
loweringMatchers = [];
|
|
699
708
|
nillablePropNames = new Set;
|
|
709
|
+
stringValueNames = new Set;
|
|
700
710
|
rootScopeNodes = new Set;
|
|
701
711
|
memoBackedLoopSlice = new Map;
|
|
702
712
|
usesHtmlTemplate = false;
|
|
@@ -854,6 +864,178 @@ function collectNestedComponents(node, result) {
|
|
|
854
864
|
}
|
|
855
865
|
}
|
|
856
866
|
|
|
867
|
+
// src/adapter/analysis/static-child-loop-bake.ts
|
|
868
|
+
import { evaluateStaticLiteral, parseExpression, resolveStaticLoopSource } from "@barefootjs/jsx";
|
|
869
|
+
function scalarToGoLiteral(value) {
|
|
870
|
+
if (typeof value === "string")
|
|
871
|
+
return `"${escapeGoString(value)}"`;
|
|
872
|
+
if (typeof value === "number")
|
|
873
|
+
return String(value);
|
|
874
|
+
if (typeof value === "boolean")
|
|
875
|
+
return value ? "true" : "false";
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
function analyzeBakeableStaticChildLoop(nested, localConstants, opts) {
|
|
879
|
+
if (!nested.loopParam || /^[{[]/.test(nested.loopParam))
|
|
880
|
+
return null;
|
|
881
|
+
const staticItemsResult = resolveStaticLoopSource(nested.loopArrayParsed, localConstants, opts);
|
|
882
|
+
if (staticItemsResult === null)
|
|
883
|
+
return null;
|
|
884
|
+
const items = [];
|
|
885
|
+
for (const item of staticItemsResult) {
|
|
886
|
+
const bindings = new Map([[nested.loopParam, item]]);
|
|
887
|
+
const inputFields = [];
|
|
888
|
+
for (const prop of nested.props) {
|
|
889
|
+
if (prop.isEventHandler)
|
|
890
|
+
continue;
|
|
891
|
+
if (prop.name.includes("-"))
|
|
892
|
+
continue;
|
|
893
|
+
const resolved = resolvePropValue(prop.value, bindings);
|
|
894
|
+
if (resolved === undefined)
|
|
895
|
+
return null;
|
|
896
|
+
const goValue = scalarToGoLiteral(resolved);
|
|
897
|
+
if (goValue === null)
|
|
898
|
+
return null;
|
|
899
|
+
inputFields.push({ goField: capitalizeFieldName(prop.name), goValue });
|
|
900
|
+
}
|
|
901
|
+
let dataKey = null;
|
|
902
|
+
if (nested.loopKey) {
|
|
903
|
+
const keyExpr = parseExpression(nested.loopKey);
|
|
904
|
+
const keyResolved = evaluateStaticLiteral(keyExpr, bindings);
|
|
905
|
+
if (keyResolved === null)
|
|
906
|
+
return null;
|
|
907
|
+
dataKey = String(keyResolved.value);
|
|
908
|
+
}
|
|
909
|
+
items.push({ inputFields, dataKey });
|
|
910
|
+
}
|
|
911
|
+
return { items };
|
|
912
|
+
}
|
|
913
|
+
function resolvePropValue(value, bindings) {
|
|
914
|
+
switch (value.kind) {
|
|
915
|
+
case "literal":
|
|
916
|
+
return value.value;
|
|
917
|
+
case "boolean-shorthand":
|
|
918
|
+
case "boolean-attr":
|
|
919
|
+
return true;
|
|
920
|
+
case "expression": {
|
|
921
|
+
if (!value.parsed)
|
|
922
|
+
return;
|
|
923
|
+
const resolved = evaluateStaticLiteral(value.parsed, bindings);
|
|
924
|
+
return resolved === null ? undefined : resolved.value;
|
|
925
|
+
}
|
|
926
|
+
default:
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// src/adapter/analysis/static-element-loop-bake.ts
|
|
932
|
+
import {
|
|
933
|
+
evaluateStaticLiteral as evaluateStaticLiteral2,
|
|
934
|
+
resolveStaticLoopSource as resolveStaticLoopSource2
|
|
935
|
+
} from "@barefootjs/jsx";
|
|
936
|
+
var ALLOWED_ATTR_EXPRESSION_KINDS = new Set([
|
|
937
|
+
"identifier",
|
|
938
|
+
"member",
|
|
939
|
+
"index-access",
|
|
940
|
+
"literal"
|
|
941
|
+
]);
|
|
942
|
+
function analyzeBakeableStaticElementLoop(loop, localConstants, opts) {
|
|
943
|
+
if (loop.childComponent)
|
|
944
|
+
return null;
|
|
945
|
+
if (loop.method === "flatMap" || loop.flatMapCallback)
|
|
946
|
+
return null;
|
|
947
|
+
if (!loop.param || /^[{[]/.test(loop.param))
|
|
948
|
+
return null;
|
|
949
|
+
if (loop.index && loop.index !== "_")
|
|
950
|
+
return null;
|
|
951
|
+
if (loop.paramBindings && loop.paramBindings.length > 0)
|
|
952
|
+
return null;
|
|
953
|
+
if (loop.filterPredicate || loop.sortComparator)
|
|
954
|
+
return null;
|
|
955
|
+
if (loop.iterationShape || loop.objectIteration)
|
|
956
|
+
return null;
|
|
957
|
+
if (loop.bodyIsMultiRoot || loop.bodyIsItemConditional)
|
|
958
|
+
return null;
|
|
959
|
+
if (!isFoldableTree(loop.children))
|
|
960
|
+
return null;
|
|
961
|
+
const items = resolveStaticLoopSource2(loop.arrayParsed, localConstants, opts);
|
|
962
|
+
if (items === null)
|
|
963
|
+
return null;
|
|
964
|
+
for (const item of items) {
|
|
965
|
+
const bindings = new Map([[loop.param, item]]);
|
|
966
|
+
if (!allExpressionsFoldFor(loop.children, bindings))
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
return { items };
|
|
970
|
+
}
|
|
971
|
+
function isFoldableTree(nodes) {
|
|
972
|
+
for (const node of nodes) {
|
|
973
|
+
switch (node.type) {
|
|
974
|
+
case "text":
|
|
975
|
+
case "expression":
|
|
976
|
+
continue;
|
|
977
|
+
case "element":
|
|
978
|
+
if (!isFoldableAttrs(node))
|
|
979
|
+
return false;
|
|
980
|
+
if (!isFoldableTree(node.children))
|
|
981
|
+
return false;
|
|
982
|
+
continue;
|
|
983
|
+
default:
|
|
984
|
+
return false;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return true;
|
|
988
|
+
}
|
|
989
|
+
function isFoldableAttrs(element) {
|
|
990
|
+
for (const attr of element.attrs) {
|
|
991
|
+
if (attr.clientOnly)
|
|
992
|
+
continue;
|
|
993
|
+
switch (attr.value.kind) {
|
|
994
|
+
case "literal":
|
|
995
|
+
case "boolean-attr":
|
|
996
|
+
case "boolean-shorthand":
|
|
997
|
+
continue;
|
|
998
|
+
case "expression":
|
|
999
|
+
if (!attr.value.parsed || !ALLOWED_ATTR_EXPRESSION_KINDS.has(attr.value.parsed.kind))
|
|
1000
|
+
return false;
|
|
1001
|
+
continue;
|
|
1002
|
+
default:
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return true;
|
|
1007
|
+
}
|
|
1008
|
+
function allExpressionsFoldFor(nodes, bindings) {
|
|
1009
|
+
for (const node of nodes) {
|
|
1010
|
+
if (node.type === "expression") {
|
|
1011
|
+
if (node.clientOnly)
|
|
1012
|
+
continue;
|
|
1013
|
+
if (!node.parsed || !resolvesToScalar(node.parsed, bindings))
|
|
1014
|
+
return false;
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
if (node.type === "element") {
|
|
1018
|
+
for (const attr of node.attrs) {
|
|
1019
|
+
if (attr.clientOnly)
|
|
1020
|
+
continue;
|
|
1021
|
+
if (attr.value.kind !== "expression")
|
|
1022
|
+
continue;
|
|
1023
|
+
if (!attr.value.parsed || !resolvesToScalar(attr.value.parsed, bindings))
|
|
1024
|
+
return false;
|
|
1025
|
+
}
|
|
1026
|
+
if (!allExpressionsFoldFor(node.children, bindings))
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return true;
|
|
1031
|
+
}
|
|
1032
|
+
function resolvesToScalar(expr, bindings) {
|
|
1033
|
+
const resolved = evaluateStaticLiteral2(expr, bindings);
|
|
1034
|
+
if (resolved === null)
|
|
1035
|
+
return false;
|
|
1036
|
+
return scalarToGoLiteral(resolved.value) !== null;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
857
1039
|
// src/adapter/expr/helper-inline.ts
|
|
858
1040
|
function inlineLocalHelperCall(ctx, jsExpr, callParsed) {
|
|
859
1041
|
if (ctx.state.localHelperNames.size === 0)
|
|
@@ -1041,7 +1223,7 @@ function forEachValueChild(n, visit) {
|
|
|
1041
1223
|
|
|
1042
1224
|
// src/adapter/expr/url-builder.ts
|
|
1043
1225
|
import {
|
|
1044
|
-
parseExpression,
|
|
1226
|
+
parseExpression as parseExpression2,
|
|
1045
1227
|
stringifyParsedExpr,
|
|
1046
1228
|
isValidHelperId
|
|
1047
1229
|
} from "@barefootjs/jsx";
|
|
@@ -1074,7 +1256,7 @@ function lowerRegisteredCall(ctx, jsExpr, preParsed) {
|
|
|
1074
1256
|
if (!call) {
|
|
1075
1257
|
if (!/^\s*[A-Za-z_$][\w$]*\s*\(/.test(jsExpr))
|
|
1076
1258
|
return null;
|
|
1077
|
-
const parsed =
|
|
1259
|
+
const parsed = parseExpression2(jsExpr);
|
|
1078
1260
|
if (parsed.kind !== "call")
|
|
1079
1261
|
return null;
|
|
1080
1262
|
call = parsed;
|
|
@@ -1116,7 +1298,7 @@ function typeInfoToGo(ctx, typeInfo, defaultValue) {
|
|
|
1116
1298
|
case "string":
|
|
1117
1299
|
return "string";
|
|
1118
1300
|
case "number":
|
|
1119
|
-
return "int";
|
|
1301
|
+
return defaultValue !== undefined ? numberPrimitiveGoType(defaultValue) : "int";
|
|
1120
1302
|
case "boolean":
|
|
1121
1303
|
return "bool";
|
|
1122
1304
|
default:
|
|
@@ -1167,6 +1349,9 @@ function tsTypeStringToGo(ctx, tsType) {
|
|
|
1167
1349
|
return t;
|
|
1168
1350
|
return "interface{}";
|
|
1169
1351
|
}
|
|
1352
|
+
function numberPrimitiveGoType(value) {
|
|
1353
|
+
return /^-?\d+\.\d+$/.test(value) ? "float64" : "int";
|
|
1354
|
+
}
|
|
1170
1355
|
function inferTypeFromValue(value) {
|
|
1171
1356
|
if (value === "true" || value === "false")
|
|
1172
1357
|
return "bool";
|
|
@@ -1279,9 +1464,9 @@ function convertInitialValue(ctx, value, typeInfo, propsParams, preParsed) {
|
|
|
1279
1464
|
return value === "true" ? "true" : "false";
|
|
1280
1465
|
}
|
|
1281
1466
|
if (typeInfo.primitive === "number") {
|
|
1282
|
-
if (
|
|
1467
|
+
if (/^-?\d+$/.test(value))
|
|
1283
1468
|
return value;
|
|
1284
|
-
if (
|
|
1469
|
+
if (/^-?\d+\.\d+$/.test(value))
|
|
1285
1470
|
return value;
|
|
1286
1471
|
return "0";
|
|
1287
1472
|
}
|
|
@@ -1306,6 +1491,12 @@ function convertInitialValue(ctx, value, typeInfo, propsParams, preParsed) {
|
|
|
1306
1491
|
}
|
|
1307
1492
|
return '""';
|
|
1308
1493
|
}
|
|
1494
|
+
if (ctx.state.localStructFields.has(typeInfo.raw)) {
|
|
1495
|
+
const baked = jsLiteralToGo(ctx, typeInfo, preParsed);
|
|
1496
|
+
if (baked !== null)
|
|
1497
|
+
return baked;
|
|
1498
|
+
return `${typeInfo.raw}{}`;
|
|
1499
|
+
}
|
|
1309
1500
|
}
|
|
1310
1501
|
return "nil";
|
|
1311
1502
|
}
|
|
@@ -1996,10 +2187,11 @@ function memoInitialFromParsedBody(ctx, body, signals, propsParams, propFallback
|
|
|
1996
2187
|
const operand = String(body.right.value);
|
|
1997
2188
|
const depName = getterCallName(body.left);
|
|
1998
2189
|
if (depName) {
|
|
1999
|
-
const
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2190
|
+
const depInitial = resolveGetterValueAsGo(ctx, depName, signals, propsParams, propFallbackVars, resolving);
|
|
2191
|
+
const isArithmeticSafe = depInitial !== null && !depInitial.startsWith("func(");
|
|
2192
|
+
if (isArithmeticSafe) {
|
|
2193
|
+
const wrapped = /\s/.test(depInitial) ? `(${depInitial})` : depInitial;
|
|
2194
|
+
return `${wrapped} ${operator} ${operand}`;
|
|
2003
2195
|
}
|
|
2004
2196
|
}
|
|
2005
2197
|
const propName = propsMemberName(body.left);
|
|
@@ -2034,9 +2226,9 @@ function memoInitialFromParsedBody(ctx, body, signals, propsParams, propFallback
|
|
|
2034
2226
|
}
|
|
2035
2227
|
const simpleDep = getterCallName(body);
|
|
2036
2228
|
if (simpleDep) {
|
|
2037
|
-
const
|
|
2038
|
-
if (
|
|
2039
|
-
return
|
|
2229
|
+
const depInitial = resolveGetterValueAsGo(ctx, simpleDep, signals, propsParams, propFallbackVars, resolving);
|
|
2230
|
+
if (depInitial !== null) {
|
|
2231
|
+
return depInitial;
|
|
2040
2232
|
}
|
|
2041
2233
|
}
|
|
2042
2234
|
const simpleProp = propsMemberName(body);
|
|
@@ -2158,7 +2350,7 @@ function propsAccessNameFromParsed2(ctx, node) {
|
|
|
2158
2350
|
|
|
2159
2351
|
// src/adapter/spread/spread-codegen.ts
|
|
2160
2352
|
import ts2 from "typescript";
|
|
2161
|
-
import { parseExpression as
|
|
2353
|
+
import { parseExpression as parseExpression3, parseRecordIndexAccess } from "@barefootjs/jsx";
|
|
2162
2354
|
function collectSpreadSlots(ctx, node) {
|
|
2163
2355
|
const result = [];
|
|
2164
2356
|
collectSpreadSlotsRecursive(ctx, node, result);
|
|
@@ -2265,7 +2457,7 @@ function parsedObjectLiteralToGoMap(parsed) {
|
|
|
2265
2457
|
}
|
|
2266
2458
|
function buildSpreadInitializer(ctx, spreadExpr, ir, parsed) {
|
|
2267
2459
|
const trimmed = spreadExpr.trim();
|
|
2268
|
-
const conditionalTree = parsed ??
|
|
2460
|
+
const conditionalTree = parsed ?? parseExpression3(trimmed);
|
|
2269
2461
|
const conditional = buildConditionalSpreadInitializer(ctx, conditionalTree, ir);
|
|
2270
2462
|
if (conditional !== undefined)
|
|
2271
2463
|
return conditional;
|
|
@@ -2296,7 +2488,7 @@ function buildSpreadInitializer(ctx, spreadExpr, ir, parsed) {
|
|
|
2296
2488
|
if (localConst?.value !== undefined) {
|
|
2297
2489
|
const initTrimmed = localConst.value.trim();
|
|
2298
2490
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(initTrimmed)) {
|
|
2299
|
-
const resolved = buildConditionalSpreadInitializer(ctx,
|
|
2491
|
+
const resolved = buildConditionalSpreadInitializer(ctx, parseExpression3(initTrimmed), ir);
|
|
2300
2492
|
if (resolved)
|
|
2301
2493
|
return resolved;
|
|
2302
2494
|
if (resolved === null)
|
|
@@ -2427,8 +2619,47 @@ function buildPropTypeOverrides(ctx, ir) {
|
|
|
2427
2619
|
}
|
|
2428
2620
|
}
|
|
2429
2621
|
}
|
|
2622
|
+
for (const propName of collectToFixedPropNames(ir.root)) {
|
|
2623
|
+
const param = ir.metadata.propsParams.find((p) => p.name === propName);
|
|
2624
|
+
if (!param)
|
|
2625
|
+
continue;
|
|
2626
|
+
const resolved = overrides.get(propName) ?? typeInfoToGo(ctx, param.type, param.defaultValue);
|
|
2627
|
+
if (resolved === "int") {
|
|
2628
|
+
overrides.set(propName, "float64");
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2430
2631
|
return overrides;
|
|
2431
2632
|
}
|
|
2633
|
+
function collectToFixedPropNames(root) {
|
|
2634
|
+
const names = new Set;
|
|
2635
|
+
const checkExpr = (expr) => {
|
|
2636
|
+
if (expr?.kind === "array-method" && expr.method === "toFixed" && expr.object.kind === "identifier") {
|
|
2637
|
+
names.add(expr.object.name);
|
|
2638
|
+
}
|
|
2639
|
+
};
|
|
2640
|
+
const walk = (node) => {
|
|
2641
|
+
if (!node)
|
|
2642
|
+
return;
|
|
2643
|
+
if (node.type === "expression")
|
|
2644
|
+
checkExpr(node.parsed);
|
|
2645
|
+
if (node.type === "conditional") {
|
|
2646
|
+
checkExpr(node.parsedCondition);
|
|
2647
|
+
walk(node.whenTrue);
|
|
2648
|
+
walk(node.whenFalse);
|
|
2649
|
+
}
|
|
2650
|
+
if (node.type === "element") {
|
|
2651
|
+
for (const attr of node.attrs) {
|
|
2652
|
+
if (attr.value.kind === "expression")
|
|
2653
|
+
checkExpr(attr.value.parsed);
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
if ("children" in node && Array.isArray(node.children)) {
|
|
2657
|
+
node.children.forEach(walk);
|
|
2658
|
+
}
|
|
2659
|
+
};
|
|
2660
|
+
walk(root);
|
|
2661
|
+
return names;
|
|
2662
|
+
}
|
|
2432
2663
|
function resolvePropGoType(ctx, param, propTypeOverrides) {
|
|
2433
2664
|
const base = propTypeOverrides.get(param.name) ?? typeInfoToGo(ctx, param.type, param.defaultValue);
|
|
2434
2665
|
if (param.optional && ctx.state.localStructFields.has(base)) {
|
|
@@ -2447,6 +2678,38 @@ function collectNillablePropNames(ctx, ir) {
|
|
|
2447
2678
|
return nillable;
|
|
2448
2679
|
}
|
|
2449
2680
|
|
|
2681
|
+
// src/adapter/props/prop-classes.ts
|
|
2682
|
+
import { collectLoopBoundNames } from "@barefootjs/jsx";
|
|
2683
|
+
function isStringTypeInfo(type) {
|
|
2684
|
+
return type.kind === "primitive" && type.primitive === "string";
|
|
2685
|
+
}
|
|
2686
|
+
function isBareStringLiteral(initialValue) {
|
|
2687
|
+
if (!initialValue)
|
|
2688
|
+
return false;
|
|
2689
|
+
const v = initialValue.trim();
|
|
2690
|
+
return v.startsWith("'") && v.endsWith("'") || v.startsWith('"') && v.endsWith('"');
|
|
2691
|
+
}
|
|
2692
|
+
function collectStringValueNames(ir) {
|
|
2693
|
+
const names = new Set;
|
|
2694
|
+
for (const s of ir.metadata.signals) {
|
|
2695
|
+
if (isStringTypeInfo(s.type) || isBareStringLiteral(s.initialValue)) {
|
|
2696
|
+
names.add(s.getter);
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
for (const p of ir.metadata.propsParams) {
|
|
2700
|
+
if (isStringTypeInfo(p.type))
|
|
2701
|
+
names.add(p.name);
|
|
2702
|
+
}
|
|
2703
|
+
for (const c of ir.metadata.localConstants) {
|
|
2704
|
+
if (c.type !== null && isStringTypeInfo(c.type) || isBareStringLiteral(c.value)) {
|
|
2705
|
+
names.add(c.name);
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
for (const bound of collectLoopBoundNames(ir))
|
|
2709
|
+
names.delete(bound);
|
|
2710
|
+
return names;
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2450
2713
|
// src/adapter/go-template-adapter.ts
|
|
2451
2714
|
var STRING_METHODS = new Set([
|
|
2452
2715
|
"replace",
|
|
@@ -2488,12 +2751,16 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2488
2751
|
return this.state.errors;
|
|
2489
2752
|
}
|
|
2490
2753
|
inLoop = false;
|
|
2754
|
+
bakedStaticChildLoopCache = new Map;
|
|
2491
2755
|
loopParamStack = [];
|
|
2756
|
+
loopKeyDepthStack = [];
|
|
2492
2757
|
loopScalarItemStack = [];
|
|
2493
2758
|
loopWrapperStack = [];
|
|
2494
2759
|
loopVarRefCount = new Map;
|
|
2495
2760
|
loopBindingStack = [];
|
|
2496
2761
|
loopRestExcludeStack = [];
|
|
2762
|
+
staticLoopItemStack = [];
|
|
2763
|
+
staticLoopBakeFailed = false;
|
|
2497
2764
|
childComponentShapes = new Map;
|
|
2498
2765
|
childContextConsumers = new Map;
|
|
2499
2766
|
constructor(options = {}) {
|
|
@@ -2509,6 +2776,8 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2509
2776
|
this.state.restPropsName = ir.metadata.restPropsName ?? null;
|
|
2510
2777
|
this.state.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
|
|
2511
2778
|
this.state.localConstants = ir.metadata.localConstants ?? [];
|
|
2779
|
+
this.state.staticLoopSourceBoundNames = collectLoopBoundNames2(ir);
|
|
2780
|
+
this.bakedStaticChildLoopCache = new Map;
|
|
2512
2781
|
this.state.localHelperNames = new Set(this.state.localConstants.filter((c) => !c.isModule && c.containsArrow).map((c) => c.name));
|
|
2513
2782
|
this.state.currentMemos = ir.metadata.memos ?? [];
|
|
2514
2783
|
this.state.currentTypeDefinitions = ir.metadata.typeDefinitions ?? [];
|
|
@@ -2536,6 +2805,7 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2536
2805
|
this.state.pendingChildrenDefines = [];
|
|
2537
2806
|
this.primeCompileState(ir);
|
|
2538
2807
|
this.state.nillablePropNames = collectNillablePropNames(this.emitCtx, ir);
|
|
2808
|
+
this.state.stringValueNames = collectStringValueNames(ir);
|
|
2539
2809
|
if (!options?.siblingTemplatesRegistered) {
|
|
2540
2810
|
this.checkImportedLoopChildComponents(ir);
|
|
2541
2811
|
}
|
|
@@ -2889,6 +3159,8 @@ ${goFields.join(`
|
|
|
2889
3159
|
lines.push(` ${fieldName} ${goType}`);
|
|
2890
3160
|
}
|
|
2891
3161
|
for (const nested of inputNested) {
|
|
3162
|
+
if (nested.loopMarkerId && this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey))
|
|
3163
|
+
continue;
|
|
2892
3164
|
lines.push(` ${nested.name}s []${nested.name}Input`);
|
|
2893
3165
|
}
|
|
2894
3166
|
const takenInput = new Set(ir.metadata.propsParams.map((p) => capitalizeFieldName(p.name)));
|
|
@@ -3020,6 +3292,21 @@ ${goFields.join(`
|
|
|
3020
3292
|
const staticWithoutBody = staticNested.filter((n) => !n.bodyChildren || n.bodyChildren.length === 0);
|
|
3021
3293
|
for (const nested of staticWithoutBody) {
|
|
3022
3294
|
const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
|
|
3295
|
+
const baked = nested.loopMarkerId ? this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey) : null;
|
|
3296
|
+
if (baked) {
|
|
3297
|
+
lines.push(` ${varName} := make([]${nested.name}Props, ${baked.items.length})`);
|
|
3298
|
+
baked.items.forEach((item, i) => {
|
|
3299
|
+
const fields = item.inputFields.map((f) => `${f.goField}: ${f.goValue}`).join(", ");
|
|
3300
|
+
lines.push(` ${varName}[${i}] = New${nested.name}Props(${nested.name}Input{${fields}})`);
|
|
3301
|
+
lines.push(` ${varName}[${i}].BfParent = scopeID`);
|
|
3302
|
+
lines.push(` ${varName}[${i}].BfMount = "${nested.slotId}"`);
|
|
3303
|
+
if (item.dataKey !== null) {
|
|
3304
|
+
lines.push(` ${varName}[${i}].BfDataKey = ${JSON.stringify(item.dataKey)}`);
|
|
3305
|
+
}
|
|
3306
|
+
});
|
|
3307
|
+
lines.push("");
|
|
3308
|
+
continue;
|
|
3309
|
+
}
|
|
3023
3310
|
lines.push(` ${varName} := make([]${nested.name}Props, len(in.${nested.name}s))`);
|
|
3024
3311
|
lines.push(` for i, item := range in.${nested.name}s {`);
|
|
3025
3312
|
lines.push(` ${varName}[i] = New${nested.name}Props(item)`);
|
|
@@ -3233,6 +3520,13 @@ ${goFields.join(`
|
|
|
3233
3520
|
break;
|
|
3234
3521
|
}
|
|
3235
3522
|
}
|
|
3523
|
+
if (parsedValue) {
|
|
3524
|
+
const goVal = parsedLiteralToGo(this.emitCtx, parsedValue);
|
|
3525
|
+
if (goVal !== null) {
|
|
3526
|
+
emitChildField(prop.name, goVal);
|
|
3527
|
+
break;
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3236
3530
|
const resolvedValue = this.resolveDynamicPropValue(exprText, ir.metadata.signals, ir.metadata.memos, ir.metadata.propsParams);
|
|
3237
3531
|
if (resolvedValue !== null) {
|
|
3238
3532
|
emitChildField(prop.name, resolvedValue);
|
|
@@ -3240,6 +3534,24 @@ ${goFields.join(`
|
|
|
3240
3534
|
break;
|
|
3241
3535
|
}
|
|
3242
3536
|
case "jsx-children":
|
|
3537
|
+
if (prop.name !== "children") {
|
|
3538
|
+
const text = this.extractTextChildren(prop.value.children);
|
|
3539
|
+
if (text !== null) {
|
|
3540
|
+
emitChildField(prop.name, JSON.stringify(text));
|
|
3541
|
+
} else {
|
|
3542
|
+
const html = this.extractHtmlChildren(prop.value.children);
|
|
3543
|
+
if (html !== null) {
|
|
3544
|
+
this.state.usesHtmlTemplate = true;
|
|
3545
|
+
emitChildField(prop.name, `template.HTML(${JSON.stringify(html)})`);
|
|
3546
|
+
} else {
|
|
3547
|
+
const scopedHtml = this.extractScopedHtmlChildren(prop.value.children);
|
|
3548
|
+
if (scopedHtml !== null) {
|
|
3549
|
+
this.state.usesHtmlTemplate = true;
|
|
3550
|
+
emitChildField(prop.name, `template.HTML(${scopedHtml})`);
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3243
3555
|
break;
|
|
3244
3556
|
}
|
|
3245
3557
|
}
|
|
@@ -3881,6 +4193,17 @@ ${goFields.join(`
|
|
|
3881
4193
|
return computeMemoInitialValueOrNull(this.emitCtx, memo, signals, propsParams, undefined, new Set([memo.name]));
|
|
3882
4194
|
}
|
|
3883
4195
|
}
|
|
4196
|
+
const propsObjectName = this.state.propsObjectName;
|
|
4197
|
+
const identifierPattern = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
4198
|
+
const bareIdentifier = identifierPattern.test(expr) ? expr : null;
|
|
4199
|
+
const barePropAccess = propsObjectName && expr.startsWith(`${propsObjectName}.`) && identifierPattern.test(expr.slice(propsObjectName.length + 1)) ? expr.slice(propsObjectName.length + 1) : null;
|
|
4200
|
+
const passthroughName = bareIdentifier ?? barePropAccess;
|
|
4201
|
+
const localConst = this.state.localConstants.find((c) => c.name === passthroughName);
|
|
4202
|
+
const isPropsDestructureAlias = localConst !== undefined && propsObjectName !== null && localConst.value === `${propsObjectName}.${passthroughName}`;
|
|
4203
|
+
const shadowedByLocal = passthroughName !== null && (localConst !== undefined && !isPropsDestructureAlias || this.state.localHelperNames.has(passthroughName));
|
|
4204
|
+
if (passthroughName && !shadowedByLocal && propsParams.some((p) => p.name === passthroughName)) {
|
|
4205
|
+
return `in.${capitalizeFieldName(passthroughName)}`;
|
|
4206
|
+
}
|
|
3884
4207
|
return null;
|
|
3885
4208
|
}
|
|
3886
4209
|
inferMemoType(memo, signals, propsParamMap) {
|
|
@@ -4005,7 +4328,7 @@ ${goFields.join(`
|
|
|
4005
4328
|
return this.renderElement(node);
|
|
4006
4329
|
}
|
|
4007
4330
|
emitText(node) {
|
|
4008
|
-
return node.value;
|
|
4331
|
+
return escapeHtml(node.value);
|
|
4009
4332
|
}
|
|
4010
4333
|
emitExpression(node) {
|
|
4011
4334
|
return this.renderExpression(node);
|
|
@@ -4037,7 +4360,8 @@ ${goFields.join(`
|
|
|
4037
4360
|
renderElement(element) {
|
|
4038
4361
|
const tag = element.tag;
|
|
4039
4362
|
const attrs = this.renderAttributes(element);
|
|
4040
|
-
const
|
|
4363
|
+
const dangerousHtml = this.renderDangerousInnerHtml(element);
|
|
4364
|
+
const children = dangerousHtml !== null ? dangerousHtml : this.renderChildren(element.children);
|
|
4041
4365
|
let hydrationAttrs = "";
|
|
4042
4366
|
if (element.needsScope) {
|
|
4043
4367
|
hydrationAttrs += ` ${this.renderScopeMarker(".ScopeID")}`;
|
|
@@ -4072,6 +4396,22 @@ ${goFields.join(`
|
|
|
4072
4396
|
}
|
|
4073
4397
|
return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
|
|
4074
4398
|
}
|
|
4399
|
+
renderDangerousInnerHtml(element) {
|
|
4400
|
+
const resolution = resolveDangerousInnerHtml(element);
|
|
4401
|
+
if (!resolution)
|
|
4402
|
+
return null;
|
|
4403
|
+
if (resolution.kind === "dynamic") {
|
|
4404
|
+
this.errors.push(dangerousInnerHtmlDiagnostic(resolution.expr, resolution.loc));
|
|
4405
|
+
return "";
|
|
4406
|
+
}
|
|
4407
|
+
const violation = dangerousInnerHtmlMetacharViolation(resolution.html, this.name);
|
|
4408
|
+
if (violation) {
|
|
4409
|
+
const attr = element.attrs.find(isDangerousInnerHtmlAttr);
|
|
4410
|
+
this.errors.push(dangerousInnerHtmlDiagnostic(`{ __html: ${JSON.stringify(resolution.html)} }`, attr.loc, violation));
|
|
4411
|
+
return "";
|
|
4412
|
+
}
|
|
4413
|
+
return resolution.html;
|
|
4414
|
+
}
|
|
4075
4415
|
renderExpression(expr) {
|
|
4076
4416
|
if (expr.clientOnly) {
|
|
4077
4417
|
if (expr.slotId) {
|
|
@@ -4265,7 +4605,7 @@ ${goFields.join(`
|
|
|
4265
4605
|
const argsStr = args.map(emit).join(" ");
|
|
4266
4606
|
return `${calleeStr} ${argsStr}`;
|
|
4267
4607
|
}
|
|
4268
|
-
member(object, property, _computed, emit) {
|
|
4608
|
+
member(object, property, _computed, optional, emit) {
|
|
4269
4609
|
const objHO = this.higherOrderShapeOf(object);
|
|
4270
4610
|
if (property === "length" && objHO) {
|
|
4271
4611
|
const result = this.renderFilterLengthExpr(objHO, emit);
|
|
@@ -4307,6 +4647,9 @@ ${goFields.join(`
|
|
|
4307
4647
|
const obj = emit(object);
|
|
4308
4648
|
if (property === "length")
|
|
4309
4649
|
return `len ${obj}`;
|
|
4650
|
+
if (optional) {
|
|
4651
|
+
return `bf_get ${wrapIfMultiToken(obj)} ${JSON.stringify(goFieldNameForKey(property))}`;
|
|
4652
|
+
}
|
|
4310
4653
|
return `${obj}.${goFieldNameForKey(property)}`;
|
|
4311
4654
|
}
|
|
4312
4655
|
indexAccess(object, index, emit) {
|
|
@@ -4337,7 +4680,7 @@ ${goFields.join(`
|
|
|
4337
4680
|
case "<=":
|
|
4338
4681
|
return `le ${wl} ${wr}`;
|
|
4339
4682
|
case "+":
|
|
4340
|
-
return
|
|
4683
|
+
return this._emitPlus(left, right, wl, wr);
|
|
4341
4684
|
case "-":
|
|
4342
4685
|
return `bf_sub ${wl} ${wr}`;
|
|
4343
4686
|
case "*":
|
|
@@ -4350,6 +4693,15 @@ ${goFields.join(`
|
|
|
4350
4693
|
return `${l} ${op} ${r}`;
|
|
4351
4694
|
}
|
|
4352
4695
|
}
|
|
4696
|
+
_emitPlus(leftExpr, rightExpr, leftRendered, rightRendered) {
|
|
4697
|
+
if (isStringConcatBinary("+", leftExpr, rightExpr, (n) => this._isStringValueName(n))) {
|
|
4698
|
+
return `bf_concat_str ${leftRendered} ${rightRendered}`;
|
|
4699
|
+
}
|
|
4700
|
+
return `bf_add ${leftRendered} ${rightRendered}`;
|
|
4701
|
+
}
|
|
4702
|
+
_isStringValueName(name) {
|
|
4703
|
+
return this.state.stringValueNames.has(name);
|
|
4704
|
+
}
|
|
4353
4705
|
unary(op, argument, emit) {
|
|
4354
4706
|
const arg = emit(argument);
|
|
4355
4707
|
if (op === "!")
|
|
@@ -4561,6 +4913,12 @@ ${goFields.join(`
|
|
|
4561
4913
|
const recv = emit(object);
|
|
4562
4914
|
return `bf_trim ${wrapIfMultiToken(recv)}`;
|
|
4563
4915
|
}
|
|
4916
|
+
case "trimStart":
|
|
4917
|
+
case "trimEnd": {
|
|
4918
|
+
const fn = method === "trimStart" ? "bf_trim_start" : "bf_trim_end";
|
|
4919
|
+
const recv = emit(object);
|
|
4920
|
+
return `${fn} ${wrapIfMultiToken(recv)}`;
|
|
4921
|
+
}
|
|
4564
4922
|
case "toFixed": {
|
|
4565
4923
|
const recv = emit(object);
|
|
4566
4924
|
const digits = args.length >= 1 ? emit(args[0]) : "0";
|
|
@@ -4595,6 +4953,12 @@ ${goFields.join(`
|
|
|
4595
4953
|
const newS = emit(args[1]);
|
|
4596
4954
|
return `bf_replace ${wrapIfMultiToken(recv)} ${wrapIfMultiToken(oldS)} ${wrapIfMultiToken(newS)}`;
|
|
4597
4955
|
}
|
|
4956
|
+
case "replaceAll": {
|
|
4957
|
+
const recv = emit(object);
|
|
4958
|
+
const oldS = emit(args[0]);
|
|
4959
|
+
const newS = emit(args[1]);
|
|
4960
|
+
return `bf_replace_all ${wrapIfMultiToken(recv)} ${wrapIfMultiToken(oldS)} ${wrapIfMultiToken(newS)}`;
|
|
4961
|
+
}
|
|
4598
4962
|
case "repeat": {
|
|
4599
4963
|
const recv = emit(object);
|
|
4600
4964
|
const count = args.length === 0 ? "0" : emit(args[0]);
|
|
@@ -4781,8 +5145,8 @@ ${goFields.join(`
|
|
|
4781
5145
|
const value = negated ? "false" : "true";
|
|
4782
5146
|
return `len (bf_filter ${arrayExpr} "${field}" ${value})`;
|
|
4783
5147
|
}
|
|
4784
|
-
renderPredicateCondition(pred, param) {
|
|
4785
|
-
return this.renderFilterExpr(pred, param);
|
|
5148
|
+
renderPredicateCondition(pred, param, datumField) {
|
|
5149
|
+
return this.renderFilterExpr(pred, param, new Map, datumField ?? undefined);
|
|
4786
5150
|
}
|
|
4787
5151
|
needsParens(expr) {
|
|
4788
5152
|
return expr.kind === "logical" || expr.kind === "unary" || expr.kind === "conditional";
|
|
@@ -4803,21 +5167,23 @@ ${goFields.join(`
|
|
|
4803
5167
|
}
|
|
4804
5168
|
return null;
|
|
4805
5169
|
}
|
|
4806
|
-
renderFilterExpr(expr, param, localVarMap = new Map) {
|
|
5170
|
+
renderFilterExpr(expr, param, localVarMap = new Map, datumField) {
|
|
4807
5171
|
if (this.filterExprDepth === 0)
|
|
4808
5172
|
this.filterExprUnsupported = false;
|
|
4809
5173
|
this.filterExprDepth++;
|
|
4810
5174
|
try {
|
|
4811
|
-
return this.renderFilterExprNode(expr, param, localVarMap);
|
|
5175
|
+
return this.renderFilterExprNode(expr, param, localVarMap, datumField);
|
|
4812
5176
|
} finally {
|
|
4813
5177
|
this.filterExprDepth--;
|
|
4814
5178
|
}
|
|
4815
5179
|
}
|
|
4816
|
-
renderFilterExprNode(expr, param, localVarMap) {
|
|
5180
|
+
renderFilterExprNode(expr, param, localVarMap, datumField) {
|
|
5181
|
+
const paramPrefix = datumField ? `.${datumField}` : "";
|
|
5182
|
+
const paramDot = paramPrefix || ".";
|
|
4817
5183
|
switch (expr.kind) {
|
|
4818
5184
|
case "identifier": {
|
|
4819
5185
|
if (expr.name === param) {
|
|
4820
|
-
return
|
|
5186
|
+
return paramDot;
|
|
4821
5187
|
}
|
|
4822
5188
|
const signal = localVarMap.get(expr.name);
|
|
4823
5189
|
if (signal) {
|
|
@@ -4835,24 +5201,24 @@ ${goFields.join(`
|
|
|
4835
5201
|
return String(expr.value);
|
|
4836
5202
|
case "member": {
|
|
4837
5203
|
if (expr.object.kind === "identifier" && expr.object.name === param) {
|
|
4838
|
-
return
|
|
5204
|
+
return `${paramPrefix}.${capitalizeFieldName(expr.property)}`;
|
|
4839
5205
|
}
|
|
4840
5206
|
if (expr.property === "length") {
|
|
4841
5207
|
const innerHO = this.higherOrderShapeOf(expr.object);
|
|
4842
5208
|
if (innerHO && innerHO.method === "filter") {
|
|
4843
|
-
const lenExpr = this.renderFilterLengthExpr(innerHO, (e) => this.renderFilterExpr(e, param, localVarMap));
|
|
5209
|
+
const lenExpr = this.renderFilterLengthExpr(innerHO, (e) => this.renderFilterExpr(e, param, localVarMap, datumField));
|
|
4844
5210
|
if (lenExpr)
|
|
4845
5211
|
return `(${lenExpr})`;
|
|
4846
5212
|
}
|
|
4847
5213
|
}
|
|
4848
|
-
const obj = this.renderFilterExpr(expr.object, param, localVarMap);
|
|
5214
|
+
const obj = this.renderFilterExpr(expr.object, param, localVarMap, datumField);
|
|
4849
5215
|
if (this.filterExprUnsupported)
|
|
4850
5216
|
return "false";
|
|
4851
5217
|
return `${obj}.${capitalizeFieldName(expr.property)}`;
|
|
4852
5218
|
}
|
|
4853
5219
|
case "call": {
|
|
4854
5220
|
if (expr.callee.kind === "member" && expr.callee.object.kind === "identifier" && expr.callee.object.name === param) {
|
|
4855
|
-
return
|
|
5221
|
+
return `${paramPrefix}.${capitalizeFieldName(expr.callee.property)}`;
|
|
4856
5222
|
}
|
|
4857
5223
|
if (expr.callee.kind === "identifier" && expr.args.length === 0) {
|
|
4858
5224
|
return `$.${capitalizeFieldName(expr.callee.name)}`;
|
|
@@ -4860,13 +5226,13 @@ ${goFields.join(`
|
|
|
4860
5226
|
if (asCallbackMethodCall3(expr) !== null) {
|
|
4861
5227
|
return this.refuseFilterExprNode(expr);
|
|
4862
5228
|
}
|
|
4863
|
-
const result = this.renderFilterExpr(expr.callee, param, localVarMap);
|
|
5229
|
+
const result = this.renderFilterExpr(expr.callee, param, localVarMap, datumField);
|
|
4864
5230
|
if (this.filterExprUnsupported)
|
|
4865
5231
|
return "false";
|
|
4866
5232
|
return result;
|
|
4867
5233
|
}
|
|
4868
5234
|
case "unary": {
|
|
4869
|
-
const arg = this.renderFilterExpr(expr.argument, param, localVarMap);
|
|
5235
|
+
const arg = this.renderFilterExpr(expr.argument, param, localVarMap, datumField);
|
|
4870
5236
|
if (this.filterExprUnsupported)
|
|
4871
5237
|
return "false";
|
|
4872
5238
|
if (expr.op === "!") {
|
|
@@ -4879,10 +5245,10 @@ ${goFields.join(`
|
|
|
4879
5245
|
return arg;
|
|
4880
5246
|
}
|
|
4881
5247
|
case "binary": {
|
|
4882
|
-
const left = this.renderFilterExpr(expr.left, param, localVarMap);
|
|
5248
|
+
const left = this.renderFilterExpr(expr.left, param, localVarMap, datumField);
|
|
4883
5249
|
if (this.filterExprUnsupported)
|
|
4884
5250
|
return "false";
|
|
4885
|
-
const right = this.renderFilterExpr(expr.right, param, localVarMap);
|
|
5251
|
+
const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField);
|
|
4886
5252
|
if (this.filterExprUnsupported)
|
|
4887
5253
|
return "false";
|
|
4888
5254
|
switch (expr.op) {
|
|
@@ -4901,7 +5267,7 @@ ${goFields.join(`
|
|
|
4901
5267
|
case "<=":
|
|
4902
5268
|
return `le ${left} ${right}`;
|
|
4903
5269
|
case "+":
|
|
4904
|
-
return
|
|
5270
|
+
return this._emitPlus(expr.left, expr.right, left, right);
|
|
4905
5271
|
case "-":
|
|
4906
5272
|
return `bf_sub ${left} ${right}`;
|
|
4907
5273
|
case "*":
|
|
@@ -4913,10 +5279,10 @@ ${goFields.join(`
|
|
|
4913
5279
|
}
|
|
4914
5280
|
}
|
|
4915
5281
|
case "logical": {
|
|
4916
|
-
const left = this.renderFilterExpr(expr.left, param, localVarMap);
|
|
5282
|
+
const left = this.renderFilterExpr(expr.left, param, localVarMap, datumField);
|
|
4917
5283
|
if (this.filterExprUnsupported)
|
|
4918
5284
|
return "false";
|
|
4919
|
-
const right = this.renderFilterExpr(expr.right, param, localVarMap);
|
|
5285
|
+
const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField);
|
|
4920
5286
|
if (this.filterExprUnsupported)
|
|
4921
5287
|
return "false";
|
|
4922
5288
|
if (expr.op === "&&") {
|
|
@@ -4984,11 +5350,22 @@ ${goFields.join(`
|
|
|
4984
5350
|
if (trimmed === "null" || trimmed === "undefined") {
|
|
4985
5351
|
return '""';
|
|
4986
5352
|
}
|
|
5353
|
+
if (this.staticLoopItemStack.length > 0) {
|
|
5354
|
+
const top = this.staticLoopItemStack[this.staticLoopItemStack.length - 1];
|
|
5355
|
+
const parsedForBake = preParsed ?? parseExpression4(trimmed);
|
|
5356
|
+
const resolved = evaluateStaticLiteral3(parsedForBake, new Map([[top.param, top.item]]));
|
|
5357
|
+
const literal = resolved !== null ? scalarToGoLiteral(resolved.value) : null;
|
|
5358
|
+
if (literal !== null) {
|
|
5359
|
+
return literal;
|
|
5360
|
+
}
|
|
5361
|
+
this.staticLoopBakeFailed = true;
|
|
5362
|
+
return '""';
|
|
5363
|
+
}
|
|
4987
5364
|
const staticIndexed = this.resolveStaticRecordLiteralIndex(trimmed);
|
|
4988
5365
|
if (staticIndexed !== null) {
|
|
4989
5366
|
return staticIndexed;
|
|
4990
5367
|
}
|
|
4991
|
-
if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
|
|
5368
|
+
if (!this.isLoopShadowedName(trimmed) && /^[A-Za-z_$][\w$]*$/.test(trimmed)) {
|
|
4992
5369
|
const litConst = (this.state.localConstants ?? []).find((c) => c.name === trimmed);
|
|
4993
5370
|
if (litConst?.value !== undefined) {
|
|
4994
5371
|
const v = litConst.value.trim();
|
|
@@ -5006,7 +5383,7 @@ ${goFields.join(`
|
|
|
5006
5383
|
if (inlined !== null) {
|
|
5007
5384
|
return this.convertExpressionToGo(stringifyParsedExpr2(inlined), out, inlined);
|
|
5008
5385
|
}
|
|
5009
|
-
const parsed = preParsed ??
|
|
5386
|
+
const parsed = preParsed ?? parseExpression4(trimmed);
|
|
5010
5387
|
const support = isSupported(parsed);
|
|
5011
5388
|
if (!support.supported) {
|
|
5012
5389
|
this.state.errors.push({
|
|
@@ -5024,10 +5401,15 @@ ${goFields.join(`
|
|
|
5024
5401
|
out.parsed = parsed;
|
|
5025
5402
|
return this.renderParsedExpr(parsed);
|
|
5026
5403
|
}
|
|
5404
|
+
isLoopShadowedName(name) {
|
|
5405
|
+
return this.loopParamStack.length > 0 && this.loopParamStack[this.loopParamStack.length - 1] === name || this.loopVarRefCount.has(name) || this.isOuterLoopParam(name) || this.loopBindingStack.some((bindings) => bindings.has(name));
|
|
5406
|
+
}
|
|
5027
5407
|
resolveStaticRecordLiteralIndex(jsExpr) {
|
|
5028
5408
|
const m = /^([A-Za-z_$][\w$]*)\[\s*(?:'([^']*)'|"([^"]*)")\s*\]$/.exec(jsExpr) ?? /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/.exec(jsExpr);
|
|
5029
5409
|
if (!m)
|
|
5030
5410
|
return null;
|
|
5411
|
+
if (this.isLoopShadowedName(m[1]))
|
|
5412
|
+
return null;
|
|
5031
5413
|
const key = m[2] ?? m[3];
|
|
5032
5414
|
const constInfo = (this.state.localConstants ?? []).find((c) => c.name === m[1] && c.isModule);
|
|
5033
5415
|
if (constInfo?.value === undefined)
|
|
@@ -5133,7 +5515,7 @@ ${goFields.join(`
|
|
|
5133
5515
|
}
|
|
5134
5516
|
convertConditionToGo(jsCondition, preParsed) {
|
|
5135
5517
|
const trimmed = jsCondition.trim();
|
|
5136
|
-
const parsed = preParsed ??
|
|
5518
|
+
const parsed = preParsed ?? parseExpression4(trimmed);
|
|
5137
5519
|
const support = isSupported(parsed);
|
|
5138
5520
|
if (!support.supported) {
|
|
5139
5521
|
this.state.errors.push({
|
|
@@ -5270,7 +5652,7 @@ ${goFields.join(`
|
|
|
5270
5652
|
result = `le ${left} ${right}`;
|
|
5271
5653
|
break;
|
|
5272
5654
|
case "+":
|
|
5273
|
-
result =
|
|
5655
|
+
result = this._emitPlus(expr.left, expr.right, left, right);
|
|
5274
5656
|
break;
|
|
5275
5657
|
case "-":
|
|
5276
5658
|
result = `bf_sub ${left} ${right}`;
|
|
@@ -5354,6 +5736,29 @@ ${goFields.join(`
|
|
|
5354
5736
|
}
|
|
5355
5737
|
return;
|
|
5356
5738
|
}
|
|
5739
|
+
wrapperDatumField(loop) {
|
|
5740
|
+
if (!loop.childComponent)
|
|
5741
|
+
return null;
|
|
5742
|
+
for (const prop of loop.childComponent.props) {
|
|
5743
|
+
if (prop.isEventHandler)
|
|
5744
|
+
continue;
|
|
5745
|
+
if (prop.value.kind !== "expression")
|
|
5746
|
+
continue;
|
|
5747
|
+
const parsed = prop.value.parsed;
|
|
5748
|
+
const isBareParamRef = parsed ? parsed.kind === "identifier" && parsed.name === loop.param : prop.value.expr.trim() === loop.param;
|
|
5749
|
+
if (isBareParamRef)
|
|
5750
|
+
return capitalizeFieldName(prop.name);
|
|
5751
|
+
}
|
|
5752
|
+
return null;
|
|
5753
|
+
}
|
|
5754
|
+
getBakedStaticChildLoop(markerId, childComponent, arrayParsed, param, key) {
|
|
5755
|
+
if (this.bakedStaticChildLoopCache.has(markerId)) {
|
|
5756
|
+
return this.bakedStaticChildLoopCache.get(markerId) ?? null;
|
|
5757
|
+
}
|
|
5758
|
+
const result = analyzeBakeableStaticChildLoop({ props: childComponent.props, loopArrayParsed: arrayParsed, loopParam: param, loopKey: key }, this.state.localConstants, { isNameShadowed: (name) => this.state.staticLoopSourceBoundNames.has(name) });
|
|
5759
|
+
this.bakedStaticChildLoopCache.set(markerId, result);
|
|
5760
|
+
return result;
|
|
5761
|
+
}
|
|
5357
5762
|
renderLoop(loop) {
|
|
5358
5763
|
if (loop.clientOnly) {
|
|
5359
5764
|
return `{{bfComment "loop:${loop.markerId}"}}{{bfComment "/loop:${loop.markerId}"}}`;
|
|
@@ -5374,8 +5779,13 @@ ${goFields.join(`
|
|
|
5374
5779
|
}
|
|
5375
5780
|
});
|
|
5376
5781
|
}
|
|
5782
|
+
const bakedChildLoop = loop.childComponent ? this.getBakedStaticChildLoop(loop.markerId, loop.childComponent, loop.arrayParsed, loop.param, loop.key ?? undefined) : null;
|
|
5783
|
+
const bakedElementLoop = loop.childComponent ? null : analyzeBakeableStaticElementLoop(loop, this.state.localConstants, { isNameShadowed: (name) => this.state.staticLoopSourceBoundNames.has(name) });
|
|
5784
|
+
if (bakedElementLoop) {
|
|
5785
|
+
return this.renderUnrolledStaticElementLoop(loop, bakedElementLoop.items);
|
|
5786
|
+
}
|
|
5377
5787
|
const arrayName = loop.array.trim();
|
|
5378
|
-
if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
|
|
5788
|
+
if (bakedChildLoop === null && /^[A-Za-z_$][\w$]*$/.test(arrayName)) {
|
|
5379
5789
|
const arrayConst = this.state.localConstants.find((c) => c.name === arrayName);
|
|
5380
5790
|
if (arrayConst && !arrayConst.isModule && arrayConst.parsed && !this.isStringExpr(arrayConst.parsed, new Set)) {
|
|
5381
5791
|
this.state.errors.push({
|
|
@@ -5389,7 +5799,7 @@ ${goFields.join(`
|
|
|
5389
5799
|
});
|
|
5390
5800
|
}
|
|
5391
5801
|
}
|
|
5392
|
-
let goArray = this.convertExpressionToGo(loop.array);
|
|
5802
|
+
let goArray = loop.childComponent ? "" : this.convertExpressionToGo(loop.array);
|
|
5393
5803
|
const param = loop.param;
|
|
5394
5804
|
let index = loop.index || "_";
|
|
5395
5805
|
let rangeIndex = index;
|
|
@@ -5427,7 +5837,9 @@ ${goFields.join(`
|
|
|
5427
5837
|
}
|
|
5428
5838
|
this.loopScalarItemStack.push(this.scalarLiteralLoopGoType(loop.arrayParsed, loop.itemType) !== null);
|
|
5429
5839
|
this.loopWrapperStack.push(!!loop.childComponent);
|
|
5840
|
+
this.loopKeyDepthStack.push(loop.depth);
|
|
5430
5841
|
const children = this.renderChildren(loop.children);
|
|
5842
|
+
this.loopKeyDepthStack.pop();
|
|
5431
5843
|
this.loopWrapperStack.pop();
|
|
5432
5844
|
this.loopScalarItemStack.pop();
|
|
5433
5845
|
const itemMarker = this.loopItemMarker(loop);
|
|
@@ -5455,7 +5867,8 @@ ${goFields.join(`
|
|
|
5455
5867
|
if (loop.filterPredicate) {
|
|
5456
5868
|
let filterCond;
|
|
5457
5869
|
if (loop.filterPredicate.predicate) {
|
|
5458
|
-
|
|
5870
|
+
const datumField = this.wrapperDatumField(loop);
|
|
5871
|
+
filterCond = this.renderPredicateCondition(loop.filterPredicate.predicate, loop.filterPredicate.param, datumField);
|
|
5459
5872
|
} else {
|
|
5460
5873
|
filterCond = "true";
|
|
5461
5874
|
}
|
|
@@ -5463,6 +5876,38 @@ ${goFields.join(`
|
|
|
5463
5876
|
}
|
|
5464
5877
|
return `{{bfComment "loop:${loop.markerId}"}}{{range $${rangeIndex}, $${rangeValue} := ${goArray}}}${itemMarker}${children}{{end}}{{bfComment "/loop:${loop.markerId}"}}`;
|
|
5465
5878
|
}
|
|
5879
|
+
renderUnrolledStaticElementLoop(loop, items) {
|
|
5880
|
+
this.inLoop = true;
|
|
5881
|
+
this.loopWrapperStack.push(false);
|
|
5882
|
+
this.loopKeyDepthStack.push(loop.depth);
|
|
5883
|
+
this.loopScalarItemStack.push(this.scalarLiteralLoopGoType(loop.arrayParsed, loop.itemType) !== null);
|
|
5884
|
+
this.loopParamStack.push(loop.param);
|
|
5885
|
+
let body = "";
|
|
5886
|
+
for (const item of items) {
|
|
5887
|
+
this.staticLoopItemStack.push({ param: loop.param, item });
|
|
5888
|
+
body += this.renderChildren(loop.children);
|
|
5889
|
+
this.staticLoopItemStack.pop();
|
|
5890
|
+
if (this.staticLoopBakeFailed) {
|
|
5891
|
+
this.staticLoopBakeFailed = false;
|
|
5892
|
+
this.state.errors.push({
|
|
5893
|
+
code: "BF101",
|
|
5894
|
+
severity: "error",
|
|
5895
|
+
message: `Loop array \`${loop.array.trim()}\` could not be fully unrolled — an expression in the loop body did not resolve against every item as the compile-time analysis expected.`,
|
|
5896
|
+
loc: loop.loc ?? this.makeLoc(),
|
|
5897
|
+
suggestion: {
|
|
5898
|
+
message: "This indicates a bug in the Go adapter's static-loop unrolling (#2224) rather than an unsupported source pattern; please file a bug with a reproduction."
|
|
5899
|
+
}
|
|
5900
|
+
});
|
|
5901
|
+
break;
|
|
5902
|
+
}
|
|
5903
|
+
}
|
|
5904
|
+
this.loopParamStack.pop();
|
|
5905
|
+
this.loopScalarItemStack.pop();
|
|
5906
|
+
this.loopKeyDepthStack.pop();
|
|
5907
|
+
this.loopWrapperStack.pop();
|
|
5908
|
+
this.inLoop = false;
|
|
5909
|
+
return `{{bfComment "loop:${loop.markerId}"}}${body}{{bfComment "/loop:${loop.markerId}"}}`;
|
|
5910
|
+
}
|
|
5466
5911
|
loopItemMarker(loop) {
|
|
5467
5912
|
if (loop.bodyIsMultiRoot)
|
|
5468
5913
|
return `{{bfComment "bf-loop-i"}}`;
|
|
@@ -5570,7 +6015,7 @@ ${goFields.join(`
|
|
|
5570
6015
|
${children}`;
|
|
5571
6016
|
}
|
|
5572
6017
|
elementAttrEmitter = {
|
|
5573
|
-
emitLiteral: (value, name) => `${name}="${value.value}"`,
|
|
6018
|
+
emitLiteral: (value, name) => `${name}="${escapeHtml(value.value)}"`,
|
|
5574
6019
|
emitExpression: (value, name) => {
|
|
5575
6020
|
if (name === "style") {
|
|
5576
6021
|
const css = this.tryLowerStyleObject(value.expr);
|
|
@@ -5582,7 +6027,7 @@ ${children}`;
|
|
|
5582
6027
|
const body = name.startsWith("aria-") ? `${name}="true"` : name;
|
|
5583
6028
|
return `${preamble}{{if ${goCond}}}${body}{{end}}`;
|
|
5584
6029
|
}
|
|
5585
|
-
const parsed = value.parsed ??
|
|
6030
|
+
const parsed = value.parsed ?? parseExpression4(value.expr.trim());
|
|
5586
6031
|
if (parsed.kind === "conditional") {
|
|
5587
6032
|
const undef = (e) => e.kind === "identifier" && (e.name === "undefined" || e.name === "null") || e.kind === "literal" && (e.value === null || e.value === undefined);
|
|
5588
6033
|
const test = parsed.test;
|
|
@@ -5647,7 +6092,7 @@ ${children}`;
|
|
|
5647
6092
|
if (!entries)
|
|
5648
6093
|
return null;
|
|
5649
6094
|
for (const e of entries) {
|
|
5650
|
-
if (e.kind === "expr" && !isSupported(
|
|
6095
|
+
if (e.kind === "expr" && !isSupported(parseExpression4(e.expr)).supported)
|
|
5651
6096
|
return null;
|
|
5652
6097
|
}
|
|
5653
6098
|
return entries.map((e) => e.kind === "literal" ? `${this.escapeAttrText(e.cssKey)}:${this.escapeAttrText(e.value)}` : `${this.escapeAttrText(e.cssKey)}:{{${this.convertExpressionToGo(e.expr)}}}`).join(";");
|
|
@@ -5657,12 +6102,15 @@ ${children}`;
|
|
|
5657
6102
|
for (const attr of element.attrs) {
|
|
5658
6103
|
if (attr.clientOnly)
|
|
5659
6104
|
continue;
|
|
6105
|
+
if (isDangerousInnerHtmlAttr(attr))
|
|
6106
|
+
continue;
|
|
5660
6107
|
let attrName;
|
|
5661
6108
|
if (attr.name === "className")
|
|
5662
6109
|
attrName = "class";
|
|
5663
|
-
else if (attr.name === "key")
|
|
5664
|
-
|
|
5665
|
-
|
|
6110
|
+
else if (attr.name === "key") {
|
|
6111
|
+
const depth = this.loopKeyDepthStack.at(-1) ?? 0;
|
|
6112
|
+
attrName = depth > 0 ? `data-key-${depth}` : "data-key";
|
|
6113
|
+
} else
|
|
5666
6114
|
attrName = attr.name;
|
|
5667
6115
|
const lowered = emitAttrValue(attr.value, this.elementAttrEmitter, attrName);
|
|
5668
6116
|
if (lowered)
|