@barefootjs/go-template 0.18.5 → 0.19.0
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/emit-context.d.ts +11 -5
- package/dist/adapter/emit-context.d.ts.map +1 -1
- package/dist/adapter/go-template-adapter.d.ts +153 -6
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +514 -53
- package/dist/adapter/lib/compile-state.d.ts +24 -0
- package/dist/adapter/lib/compile-state.d.ts.map +1 -1
- package/dist/adapter/lib/types.d.ts +9 -0
- package/dist/adapter/lib/types.d.ts.map +1 -1
- package/dist/adapter/props/prop-classes.d.ts +28 -9
- package/dist/adapter/props/prop-classes.d.ts.map +1 -1
- package/dist/adapter/props/prop-types.d.ts +45 -0
- package/dist/adapter/props/prop-types.d.ts.map +1 -1
- package/dist/build.js +514 -53
- package/dist/conformance-pins.d.ts.map +1 -1
- package/dist/index.js +516 -62
- 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 +876 -21
- 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/emit-context.ts +14 -5
- package/src/adapter/go-template-adapter.ts +620 -45
- package/src/adapter/lib/compile-state.ts +27 -0
- package/src/adapter/lib/types.ts +9 -0
- package/src/adapter/props/prop-classes.ts +34 -9
- package/src/adapter/props/prop-types.ts +178 -1
- package/src/adapter/value/value-lowering.ts +1 -1
- package/src/conformance-pins.ts +30 -31
- package/src/render-divergences.ts +12 -0
- package/src/test-render.ts +127 -10
package/dist/build.js
CHANGED
|
@@ -364,8 +364,8 @@ var init_path = __esm(() => {
|
|
|
364
364
|
// src/adapter/go-template-adapter.ts
|
|
365
365
|
import {
|
|
366
366
|
BaseAdapter,
|
|
367
|
-
isBooleanAttr,
|
|
368
|
-
parseExpression as
|
|
367
|
+
isBooleanAttr as isBooleanAttr2,
|
|
368
|
+
parseExpression as parseExpression4,
|
|
369
369
|
stringifyParsedExpr as stringifyParsedExpr2,
|
|
370
370
|
parseStyleObjectEntries,
|
|
371
371
|
isSupported,
|
|
@@ -383,7 +383,13 @@ import {
|
|
|
383
383
|
prepareLoweringMatchers,
|
|
384
384
|
envSignalReaderFor,
|
|
385
385
|
computeSsrSeedPlan,
|
|
386
|
-
isStringConcatBinary
|
|
386
|
+
isStringConcatBinary,
|
|
387
|
+
isDangerousInnerHtmlAttr,
|
|
388
|
+
resolveDangerousInnerHtml,
|
|
389
|
+
dangerousInnerHtmlMetacharViolation,
|
|
390
|
+
dangerousInnerHtmlDiagnostic,
|
|
391
|
+
collectLoopBoundNames as collectLoopBoundNames2,
|
|
392
|
+
evaluateStaticLiteral as evaluateStaticLiteral3
|
|
387
393
|
} from "@barefootjs/jsx";
|
|
388
394
|
import { findInterpolationEnd } from "@barefootjs/jsx/scanner";
|
|
389
395
|
import { BF_REGION, escapeHtml } from "@barefootjs/shared";
|
|
@@ -689,6 +695,7 @@ class CompileState {
|
|
|
689
695
|
restPropsName = null;
|
|
690
696
|
moduleStringConsts = new Map;
|
|
691
697
|
localConstants = [];
|
|
698
|
+
staticLoopSourceBoundNames = new Set;
|
|
692
699
|
localHelperNames = new Set;
|
|
693
700
|
currentMemos = [];
|
|
694
701
|
currentTypeDefinitions = [];
|
|
@@ -699,6 +706,8 @@ class CompileState {
|
|
|
699
706
|
hoistedMemoLocals = new Map;
|
|
700
707
|
loweringMatchers = [];
|
|
701
708
|
nillablePropNames = new Set;
|
|
709
|
+
nullishConsumedPropNames = new Set;
|
|
710
|
+
omittableAttrConsumedPropNames = new Set;
|
|
702
711
|
stringValueNames = new Set;
|
|
703
712
|
rootScopeNodes = new Set;
|
|
704
713
|
memoBackedLoopSlice = new Map;
|
|
@@ -857,6 +866,178 @@ function collectNestedComponents(node, result) {
|
|
|
857
866
|
}
|
|
858
867
|
}
|
|
859
868
|
|
|
869
|
+
// src/adapter/analysis/static-child-loop-bake.ts
|
|
870
|
+
import { evaluateStaticLiteral, parseExpression, resolveStaticLoopSource } from "@barefootjs/jsx";
|
|
871
|
+
function scalarToGoLiteral(value) {
|
|
872
|
+
if (typeof value === "string")
|
|
873
|
+
return `"${escapeGoString(value)}"`;
|
|
874
|
+
if (typeof value === "number")
|
|
875
|
+
return String(value);
|
|
876
|
+
if (typeof value === "boolean")
|
|
877
|
+
return value ? "true" : "false";
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
function analyzeBakeableStaticChildLoop(nested, localConstants, opts) {
|
|
881
|
+
if (!nested.loopParam || /^[{[]/.test(nested.loopParam))
|
|
882
|
+
return null;
|
|
883
|
+
const staticItemsResult = resolveStaticLoopSource(nested.loopArrayParsed, localConstants, opts);
|
|
884
|
+
if (staticItemsResult === null)
|
|
885
|
+
return null;
|
|
886
|
+
const items = [];
|
|
887
|
+
for (const item of staticItemsResult) {
|
|
888
|
+
const bindings = new Map([[nested.loopParam, item]]);
|
|
889
|
+
const inputFields = [];
|
|
890
|
+
for (const prop of nested.props) {
|
|
891
|
+
if (prop.isEventHandler)
|
|
892
|
+
continue;
|
|
893
|
+
if (prop.name.includes("-"))
|
|
894
|
+
continue;
|
|
895
|
+
const resolved = resolvePropValue(prop.value, bindings);
|
|
896
|
+
if (resolved === undefined)
|
|
897
|
+
return null;
|
|
898
|
+
const goValue = scalarToGoLiteral(resolved);
|
|
899
|
+
if (goValue === null)
|
|
900
|
+
return null;
|
|
901
|
+
inputFields.push({ goField: capitalizeFieldName(prop.name), goValue });
|
|
902
|
+
}
|
|
903
|
+
let dataKey = null;
|
|
904
|
+
if (nested.loopKey) {
|
|
905
|
+
const keyExpr = parseExpression(nested.loopKey);
|
|
906
|
+
const keyResolved = evaluateStaticLiteral(keyExpr, bindings);
|
|
907
|
+
if (keyResolved === null)
|
|
908
|
+
return null;
|
|
909
|
+
dataKey = String(keyResolved.value);
|
|
910
|
+
}
|
|
911
|
+
items.push({ inputFields, dataKey });
|
|
912
|
+
}
|
|
913
|
+
return { items };
|
|
914
|
+
}
|
|
915
|
+
function resolvePropValue(value, bindings) {
|
|
916
|
+
switch (value.kind) {
|
|
917
|
+
case "literal":
|
|
918
|
+
return value.value;
|
|
919
|
+
case "boolean-shorthand":
|
|
920
|
+
case "boolean-attr":
|
|
921
|
+
return true;
|
|
922
|
+
case "expression": {
|
|
923
|
+
if (!value.parsed)
|
|
924
|
+
return;
|
|
925
|
+
const resolved = evaluateStaticLiteral(value.parsed, bindings);
|
|
926
|
+
return resolved === null ? undefined : resolved.value;
|
|
927
|
+
}
|
|
928
|
+
default:
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// src/adapter/analysis/static-element-loop-bake.ts
|
|
934
|
+
import {
|
|
935
|
+
evaluateStaticLiteral as evaluateStaticLiteral2,
|
|
936
|
+
resolveStaticLoopSource as resolveStaticLoopSource2
|
|
937
|
+
} from "@barefootjs/jsx";
|
|
938
|
+
var ALLOWED_ATTR_EXPRESSION_KINDS = new Set([
|
|
939
|
+
"identifier",
|
|
940
|
+
"member",
|
|
941
|
+
"index-access",
|
|
942
|
+
"literal"
|
|
943
|
+
]);
|
|
944
|
+
function analyzeBakeableStaticElementLoop(loop, localConstants, opts) {
|
|
945
|
+
if (loop.childComponent)
|
|
946
|
+
return null;
|
|
947
|
+
if (loop.method === "flatMap" || loop.flatMapCallback)
|
|
948
|
+
return null;
|
|
949
|
+
if (!loop.param || /^[{[]/.test(loop.param))
|
|
950
|
+
return null;
|
|
951
|
+
if (loop.index && loop.index !== "_")
|
|
952
|
+
return null;
|
|
953
|
+
if (loop.paramBindings && loop.paramBindings.length > 0)
|
|
954
|
+
return null;
|
|
955
|
+
if (loop.filterPredicate || loop.sortComparator)
|
|
956
|
+
return null;
|
|
957
|
+
if (loop.iterationShape || loop.objectIteration)
|
|
958
|
+
return null;
|
|
959
|
+
if (loop.bodyIsMultiRoot || loop.bodyIsItemConditional)
|
|
960
|
+
return null;
|
|
961
|
+
if (!isFoldableTree(loop.children))
|
|
962
|
+
return null;
|
|
963
|
+
const items = resolveStaticLoopSource2(loop.arrayParsed, localConstants, opts);
|
|
964
|
+
if (items === null)
|
|
965
|
+
return null;
|
|
966
|
+
for (const item of items) {
|
|
967
|
+
const bindings = new Map([[loop.param, item]]);
|
|
968
|
+
if (!allExpressionsFoldFor(loop.children, bindings))
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
return { items };
|
|
972
|
+
}
|
|
973
|
+
function isFoldableTree(nodes) {
|
|
974
|
+
for (const node of nodes) {
|
|
975
|
+
switch (node.type) {
|
|
976
|
+
case "text":
|
|
977
|
+
case "expression":
|
|
978
|
+
continue;
|
|
979
|
+
case "element":
|
|
980
|
+
if (!isFoldableAttrs(node))
|
|
981
|
+
return false;
|
|
982
|
+
if (!isFoldableTree(node.children))
|
|
983
|
+
return false;
|
|
984
|
+
continue;
|
|
985
|
+
default:
|
|
986
|
+
return false;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
return true;
|
|
990
|
+
}
|
|
991
|
+
function isFoldableAttrs(element) {
|
|
992
|
+
for (const attr of element.attrs) {
|
|
993
|
+
if (attr.clientOnly)
|
|
994
|
+
continue;
|
|
995
|
+
switch (attr.value.kind) {
|
|
996
|
+
case "literal":
|
|
997
|
+
case "boolean-attr":
|
|
998
|
+
case "boolean-shorthand":
|
|
999
|
+
continue;
|
|
1000
|
+
case "expression":
|
|
1001
|
+
if (!attr.value.parsed || !ALLOWED_ATTR_EXPRESSION_KINDS.has(attr.value.parsed.kind))
|
|
1002
|
+
return false;
|
|
1003
|
+
continue;
|
|
1004
|
+
default:
|
|
1005
|
+
return false;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return true;
|
|
1009
|
+
}
|
|
1010
|
+
function allExpressionsFoldFor(nodes, bindings) {
|
|
1011
|
+
for (const node of nodes) {
|
|
1012
|
+
if (node.type === "expression") {
|
|
1013
|
+
if (node.clientOnly)
|
|
1014
|
+
continue;
|
|
1015
|
+
if (!node.parsed || !resolvesToScalar(node.parsed, bindings))
|
|
1016
|
+
return false;
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
if (node.type === "element") {
|
|
1020
|
+
for (const attr of node.attrs) {
|
|
1021
|
+
if (attr.clientOnly)
|
|
1022
|
+
continue;
|
|
1023
|
+
if (attr.value.kind !== "expression")
|
|
1024
|
+
continue;
|
|
1025
|
+
if (!attr.value.parsed || !resolvesToScalar(attr.value.parsed, bindings))
|
|
1026
|
+
return false;
|
|
1027
|
+
}
|
|
1028
|
+
if (!allExpressionsFoldFor(node.children, bindings))
|
|
1029
|
+
return false;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
return true;
|
|
1033
|
+
}
|
|
1034
|
+
function resolvesToScalar(expr, bindings) {
|
|
1035
|
+
const resolved = evaluateStaticLiteral2(expr, bindings);
|
|
1036
|
+
if (resolved === null)
|
|
1037
|
+
return false;
|
|
1038
|
+
return scalarToGoLiteral(resolved.value) !== null;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
860
1041
|
// src/adapter/expr/helper-inline.ts
|
|
861
1042
|
function inlineLocalHelperCall(ctx, jsExpr, callParsed) {
|
|
862
1043
|
if (ctx.state.localHelperNames.size === 0)
|
|
@@ -1044,7 +1225,7 @@ function forEachValueChild(n, visit) {
|
|
|
1044
1225
|
|
|
1045
1226
|
// src/adapter/expr/url-builder.ts
|
|
1046
1227
|
import {
|
|
1047
|
-
parseExpression,
|
|
1228
|
+
parseExpression as parseExpression2,
|
|
1048
1229
|
stringifyParsedExpr,
|
|
1049
1230
|
isValidHelperId
|
|
1050
1231
|
} from "@barefootjs/jsx";
|
|
@@ -1077,7 +1258,7 @@ function lowerRegisteredCall(ctx, jsExpr, preParsed) {
|
|
|
1077
1258
|
if (!call) {
|
|
1078
1259
|
if (!/^\s*[A-Za-z_$][\w$]*\s*\(/.test(jsExpr))
|
|
1079
1260
|
return null;
|
|
1080
|
-
const parsed =
|
|
1261
|
+
const parsed = parseExpression2(jsExpr);
|
|
1081
1262
|
if (parsed.kind !== "call")
|
|
1082
1263
|
return null;
|
|
1083
1264
|
call = parsed;
|
|
@@ -1276,7 +1457,7 @@ function convertInitialValue(ctx, value, typeInfo, propsParams, preParsed) {
|
|
|
1276
1457
|
return `in.${capitalizeFieldName(value)}`;
|
|
1277
1458
|
}
|
|
1278
1459
|
}
|
|
1279
|
-
const propName = ctx.extractPropNameFromInitialValue(value);
|
|
1460
|
+
const propName = ctx.extractPropNameFromInitialValue(value, preParsed);
|
|
1280
1461
|
if (propName && propsParams?.some((p) => p.name === propName)) {
|
|
1281
1462
|
return `in.${capitalizeFieldName(propName)}`;
|
|
1282
1463
|
}
|
|
@@ -2171,7 +2352,7 @@ function propsAccessNameFromParsed2(ctx, node) {
|
|
|
2171
2352
|
|
|
2172
2353
|
// src/adapter/spread/spread-codegen.ts
|
|
2173
2354
|
import ts2 from "typescript";
|
|
2174
|
-
import { parseExpression as
|
|
2355
|
+
import { parseExpression as parseExpression3, parseRecordIndexAccess } from "@barefootjs/jsx";
|
|
2175
2356
|
function collectSpreadSlots(ctx, node) {
|
|
2176
2357
|
const result = [];
|
|
2177
2358
|
collectSpreadSlotsRecursive(ctx, node, result);
|
|
@@ -2278,7 +2459,7 @@ function parsedObjectLiteralToGoMap(parsed) {
|
|
|
2278
2459
|
}
|
|
2279
2460
|
function buildSpreadInitializer(ctx, spreadExpr, ir, parsed) {
|
|
2280
2461
|
const trimmed = spreadExpr.trim();
|
|
2281
|
-
const conditionalTree = parsed ??
|
|
2462
|
+
const conditionalTree = parsed ?? parseExpression3(trimmed);
|
|
2282
2463
|
const conditional = buildConditionalSpreadInitializer(ctx, conditionalTree, ir);
|
|
2283
2464
|
if (conditional !== undefined)
|
|
2284
2465
|
return conditional;
|
|
@@ -2309,7 +2490,7 @@ function buildSpreadInitializer(ctx, spreadExpr, ir, parsed) {
|
|
|
2309
2490
|
if (localConst?.value !== undefined) {
|
|
2310
2491
|
const initTrimmed = localConst.value.trim();
|
|
2311
2492
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(initTrimmed)) {
|
|
2312
|
-
const resolved = buildConditionalSpreadInitializer(ctx,
|
|
2493
|
+
const resolved = buildConditionalSpreadInitializer(ctx, parseExpression3(initTrimmed), ir);
|
|
2313
2494
|
if (resolved)
|
|
2314
2495
|
return resolved;
|
|
2315
2496
|
if (resolved === null)
|
|
@@ -2420,11 +2601,12 @@ function recordIndexAccessToGoMap(ctx, val, ir) {
|
|
|
2420
2601
|
}
|
|
2421
2602
|
|
|
2422
2603
|
// src/adapter/props/prop-types.ts
|
|
2604
|
+
import { isBooleanAttr } from "@barefootjs/jsx";
|
|
2423
2605
|
function buildPropTypeOverrides(ctx, ir) {
|
|
2424
2606
|
const overrides = new Map;
|
|
2425
2607
|
for (const signal of ir.metadata.signals) {
|
|
2426
2608
|
const propNames = [signal.initialValue];
|
|
2427
|
-
const extracted = ctx.extractPropNameFromInitialValue(signal.initialValue);
|
|
2609
|
+
const extracted = ctx.extractPropNameFromInitialValue(signal.initialValue, signal.parsed);
|
|
2428
2610
|
if (extracted)
|
|
2429
2611
|
propNames.push(extracted);
|
|
2430
2612
|
for (const propName of propNames) {
|
|
@@ -2481,11 +2663,96 @@ function collectToFixedPropNames(root) {
|
|
|
2481
2663
|
walk(root);
|
|
2482
2664
|
return names;
|
|
2483
2665
|
}
|
|
2666
|
+
var NULLISH_SCALAR_GO_TYPES = new Set(["string", "int", "float64", "bool"]);
|
|
2667
|
+
function collectNullishConsumedPropNames(ctx, ir) {
|
|
2668
|
+
const names = new Set;
|
|
2669
|
+
const optionalParams = new Set(ir.metadata.propsParams.filter((p) => p.optional && p.defaultValue == null).map((p) => p.name));
|
|
2670
|
+
if (optionalParams.size === 0)
|
|
2671
|
+
return names;
|
|
2672
|
+
const propsObject = ctx.state.propsObjectName;
|
|
2673
|
+
const propNameOfLeft = (left) => {
|
|
2674
|
+
if (left.kind === "identifier")
|
|
2675
|
+
return left.name;
|
|
2676
|
+
if (left.kind === "member" && !left.computed && left.object.kind === "identifier" && left.object.name === propsObject) {
|
|
2677
|
+
return left.property;
|
|
2678
|
+
}
|
|
2679
|
+
return null;
|
|
2680
|
+
};
|
|
2681
|
+
const isZeroEquivalentLiteral = (right) => right.kind === "literal" && (right.value === "" || right.value === false || right.value === null || right.literalType === "number" && Number(right.value) === 0);
|
|
2682
|
+
const walk = (node) => {
|
|
2683
|
+
if (!node || typeof node !== "object")
|
|
2684
|
+
return;
|
|
2685
|
+
if (Array.isArray(node)) {
|
|
2686
|
+
for (const item of node)
|
|
2687
|
+
walk(item);
|
|
2688
|
+
return;
|
|
2689
|
+
}
|
|
2690
|
+
const rec = node;
|
|
2691
|
+
if (rec.kind === "logical" && rec.op === "??" && rec.left && rec.right) {
|
|
2692
|
+
const propName = propNameOfLeft(rec.left);
|
|
2693
|
+
if (propName && optionalParams.has(propName) && !isZeroEquivalentLiteral(rec.right)) {
|
|
2694
|
+
names.add(propName);
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
for (const value of Object.values(rec))
|
|
2698
|
+
walk(value);
|
|
2699
|
+
};
|
|
2700
|
+
walk(ir.root);
|
|
2701
|
+
for (const signal of ir.metadata.signals) {
|
|
2702
|
+
const match = ctx.extractPropFallback(signal.initialValue, signal.parsed);
|
|
2703
|
+
if (!match || !optionalParams.has(match.propName))
|
|
2704
|
+
continue;
|
|
2705
|
+
const f = match.goFallback;
|
|
2706
|
+
if (f === '""' || f === "false" || f === "nil" || Number(f) === 0)
|
|
2707
|
+
continue;
|
|
2708
|
+
names.add(match.propName);
|
|
2709
|
+
}
|
|
2710
|
+
return names;
|
|
2711
|
+
}
|
|
2712
|
+
function collectOmittableAttrConsumedPropNames(ctx, ir) {
|
|
2713
|
+
const names = new Set;
|
|
2714
|
+
const optionalParams = new Set(ir.metadata.propsParams.filter((p) => p.optional && p.defaultValue == null).map((p) => p.name));
|
|
2715
|
+
if (optionalParams.size === 0)
|
|
2716
|
+
return names;
|
|
2717
|
+
const propsObject = ctx.state.propsObjectName;
|
|
2718
|
+
const walk = (node) => {
|
|
2719
|
+
if (!node || typeof node !== "object")
|
|
2720
|
+
return;
|
|
2721
|
+
if (Array.isArray(node)) {
|
|
2722
|
+
for (const item of node)
|
|
2723
|
+
walk(item);
|
|
2724
|
+
return;
|
|
2725
|
+
}
|
|
2726
|
+
const rec = node;
|
|
2727
|
+
if (rec.type === "element" && Array.isArray(rec.attrs)) {
|
|
2728
|
+
for (const attr of rec.attrs) {
|
|
2729
|
+
if (attr.name === "class" || attr.name === "className" || attr.name === "style")
|
|
2730
|
+
continue;
|
|
2731
|
+
if (isBooleanAttr(attr.name))
|
|
2732
|
+
continue;
|
|
2733
|
+
if (attr.value?.kind !== "expression" || attr.value.presenceOrUndefined)
|
|
2734
|
+
continue;
|
|
2735
|
+
const bareId = String(attr.value.expr ?? "").trim();
|
|
2736
|
+
const propName = propsObject && bareId.startsWith(`${propsObject}.`) ? bareId.slice(propsObject.length + 1) : bareId;
|
|
2737
|
+
if (/^[A-Za-z_$][\w$]*$/.test(propName) && optionalParams.has(propName)) {
|
|
2738
|
+
names.add(propName);
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
for (const value of Object.values(rec))
|
|
2743
|
+
walk(value);
|
|
2744
|
+
};
|
|
2745
|
+
walk(ir.root);
|
|
2746
|
+
return names;
|
|
2747
|
+
}
|
|
2484
2748
|
function resolvePropGoType(ctx, param, propTypeOverrides) {
|
|
2485
2749
|
const base = propTypeOverrides.get(param.name) ?? typeInfoToGo(ctx, param.type, param.defaultValue);
|
|
2486
2750
|
if (param.optional && ctx.state.localStructFields.has(base)) {
|
|
2487
2751
|
return "map[string]interface{}";
|
|
2488
2752
|
}
|
|
2753
|
+
if (param.optional && param.type.kind === "primitive" && (ctx.state.nullishConsumedPropNames.has(param.name) || ctx.state.omittableAttrConsumedPropNames.has(param.name)) && NULLISH_SCALAR_GO_TYPES.has(base)) {
|
|
2754
|
+
return "interface{}";
|
|
2755
|
+
}
|
|
2489
2756
|
return base;
|
|
2490
2757
|
}
|
|
2491
2758
|
function collectNillablePropNames(ctx, ir) {
|
|
@@ -2500,6 +2767,7 @@ function collectNillablePropNames(ctx, ir) {
|
|
|
2500
2767
|
}
|
|
2501
2768
|
|
|
2502
2769
|
// src/adapter/props/prop-classes.ts
|
|
2770
|
+
import { collectLoopBoundNames } from "@barefootjs/jsx";
|
|
2503
2771
|
function isStringTypeInfo(type) {
|
|
2504
2772
|
return type.kind === "primitive" && type.primitive === "string";
|
|
2505
2773
|
}
|
|
@@ -2520,6 +2788,13 @@ function collectStringValueNames(ir) {
|
|
|
2520
2788
|
if (isStringTypeInfo(p.type))
|
|
2521
2789
|
names.add(p.name);
|
|
2522
2790
|
}
|
|
2791
|
+
for (const c of ir.metadata.localConstants) {
|
|
2792
|
+
if (c.type !== null && isStringTypeInfo(c.type) || isBareStringLiteral(c.value)) {
|
|
2793
|
+
names.add(c.name);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
for (const bound of collectLoopBoundNames(ir))
|
|
2797
|
+
names.delete(bound);
|
|
2523
2798
|
return names;
|
|
2524
2799
|
}
|
|
2525
2800
|
|
|
@@ -2556,14 +2831,15 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2556
2831
|
state: this.state,
|
|
2557
2832
|
convertExpressionToGo: (jsExpr, out, preParsed) => this.convertExpressionToGo(jsExpr, out, preParsed),
|
|
2558
2833
|
convertConditionToGo: (jsCondition, preParsed) => this.convertConditionToGo(jsCondition, preParsed),
|
|
2559
|
-
extractPropNameFromInitialValue: (initialValue) => this.extractPropNameFromInitialValue(initialValue),
|
|
2560
|
-
extractPropFallback: (initialValue) => this.extractPropFallback(initialValue),
|
|
2834
|
+
extractPropNameFromInitialValue: (initialValue, preParsed) => this.extractPropNameFromInitialValue(initialValue, preParsed),
|
|
2835
|
+
extractPropFallback: (initialValue, preParsed) => this.extractPropFallback(initialValue, preParsed),
|
|
2561
2836
|
resolveModuleStringConst: (name) => this.resolveModuleStringConst(name)
|
|
2562
2837
|
};
|
|
2563
2838
|
get errors() {
|
|
2564
2839
|
return this.state.errors;
|
|
2565
2840
|
}
|
|
2566
2841
|
inLoop = false;
|
|
2842
|
+
bakedStaticChildLoopCache = new Map;
|
|
2567
2843
|
loopParamStack = [];
|
|
2568
2844
|
loopKeyDepthStack = [];
|
|
2569
2845
|
loopScalarItemStack = [];
|
|
@@ -2571,6 +2847,8 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2571
2847
|
loopVarRefCount = new Map;
|
|
2572
2848
|
loopBindingStack = [];
|
|
2573
2849
|
loopRestExcludeStack = [];
|
|
2850
|
+
staticLoopItemStack = [];
|
|
2851
|
+
staticLoopBakeFailed = false;
|
|
2574
2852
|
childComponentShapes = new Map;
|
|
2575
2853
|
childContextConsumers = new Map;
|
|
2576
2854
|
constructor(options = {}) {
|
|
@@ -2586,6 +2864,8 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2586
2864
|
this.state.restPropsName = ir.metadata.restPropsName ?? null;
|
|
2587
2865
|
this.state.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
|
|
2588
2866
|
this.state.localConstants = ir.metadata.localConstants ?? [];
|
|
2867
|
+
this.state.staticLoopSourceBoundNames = collectLoopBoundNames2(ir);
|
|
2868
|
+
this.bakedStaticChildLoopCache = new Map;
|
|
2589
2869
|
this.state.localHelperNames = new Set(this.state.localConstants.filter((c) => !c.isModule && c.containsArrow).map((c) => c.name));
|
|
2590
2870
|
this.state.currentMemos = ir.metadata.memos ?? [];
|
|
2591
2871
|
this.state.currentTypeDefinitions = ir.metadata.typeDefinitions ?? [];
|
|
@@ -2604,6 +2884,10 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2604
2884
|
}
|
|
2605
2885
|
this.state.loweringMatchers = prepareLoweringMatchers(ir.metadata);
|
|
2606
2886
|
augmentInheritedPropAccesses(ir);
|
|
2887
|
+
this.buildLocalTypeTables(ir, ir.metadata.componentName);
|
|
2888
|
+
this.state.nullishConsumedPropNames = collectNullishConsumedPropNames(this.emitCtx, ir);
|
|
2889
|
+
this.state.omittableAttrConsumedPropNames = collectOmittableAttrConsumedPropNames(this.emitCtx, ir);
|
|
2890
|
+
this.state.nillablePropNames = collectNillablePropNames(this.emitCtx, ir);
|
|
2607
2891
|
}
|
|
2608
2892
|
generate(ir, options) {
|
|
2609
2893
|
this.state.componentName = ir.metadata.componentName;
|
|
@@ -2612,7 +2896,6 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2612
2896
|
this.state.templateVarCounter = 0;
|
|
2613
2897
|
this.state.pendingChildrenDefines = [];
|
|
2614
2898
|
this.primeCompileState(ir);
|
|
2615
|
-
this.state.nillablePropNames = collectNillablePropNames(this.emitCtx, ir);
|
|
2616
2899
|
this.state.stringValueNames = collectStringValueNames(ir);
|
|
2617
2900
|
if (!options?.siblingTemplatesRegistered) {
|
|
2618
2901
|
this.checkImportedLoopChildComponents(ir);
|
|
@@ -2967,6 +3250,8 @@ ${goFields.join(`
|
|
|
2967
3250
|
lines.push(` ${fieldName} ${goType}`);
|
|
2968
3251
|
}
|
|
2969
3252
|
for (const nested of inputNested) {
|
|
3253
|
+
if (nested.loopMarkerId && this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey))
|
|
3254
|
+
continue;
|
|
2970
3255
|
lines.push(` ${nested.name}s []${nested.name}Input`);
|
|
2971
3256
|
}
|
|
2972
3257
|
const takenInput = new Set(ir.metadata.propsParams.map((p) => capitalizeFieldName(p.name)));
|
|
@@ -3098,6 +3383,21 @@ ${goFields.join(`
|
|
|
3098
3383
|
const staticWithoutBody = staticNested.filter((n) => !n.bodyChildren || n.bodyChildren.length === 0);
|
|
3099
3384
|
for (const nested of staticWithoutBody) {
|
|
3100
3385
|
const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
|
|
3386
|
+
const baked = nested.loopMarkerId ? this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey) : null;
|
|
3387
|
+
if (baked) {
|
|
3388
|
+
lines.push(` ${varName} := make([]${nested.name}Props, ${baked.items.length})`);
|
|
3389
|
+
baked.items.forEach((item, i) => {
|
|
3390
|
+
const fields = item.inputFields.map((f) => `${f.goField}: ${f.goValue}`).join(", ");
|
|
3391
|
+
lines.push(` ${varName}[${i}] = New${nested.name}Props(${nested.name}Input{${fields}})`);
|
|
3392
|
+
lines.push(` ${varName}[${i}].BfParent = scopeID`);
|
|
3393
|
+
lines.push(` ${varName}[${i}].BfMount = "${nested.slotId}"`);
|
|
3394
|
+
if (item.dataKey !== null) {
|
|
3395
|
+
lines.push(` ${varName}[${i}].BfDataKey = ${JSON.stringify(item.dataKey)}`);
|
|
3396
|
+
}
|
|
3397
|
+
});
|
|
3398
|
+
lines.push("");
|
|
3399
|
+
continue;
|
|
3400
|
+
}
|
|
3101
3401
|
lines.push(` ${varName} := make([]${nested.name}Props, len(in.${nested.name}s))`);
|
|
3102
3402
|
lines.push(` for i, item := range in.${nested.name}s {`);
|
|
3103
3403
|
lines.push(` ${varName}[i] = New${nested.name}Props(item)`);
|
|
@@ -3114,10 +3414,18 @@ ${goFields.join(`
|
|
|
3114
3414
|
this.emitStaticBodyWrappers(lines, ir, componentName, staticWithBody, emittedWrapperVars);
|
|
3115
3415
|
const propFallbackVars = this.collectPropFallbackVars(ir);
|
|
3116
3416
|
for (const [, info] of propFallbackVars) {
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3417
|
+
if (info.assertType) {
|
|
3418
|
+
const deref = info.assertType === "int" ? `bf.ToInt(in.${info.fieldName})` : info.assertType === "float64" ? `bf.ToFloat64(in.${info.fieldName})` : `in.${info.fieldName}.(${info.assertType})`;
|
|
3419
|
+
lines.push(` var ${info.varName} ${info.assertType} = ${info.goFallback}`);
|
|
3420
|
+
lines.push(` if in.${info.fieldName} != nil {`);
|
|
3421
|
+
lines.push(` ${info.varName} = ${deref}`);
|
|
3422
|
+
lines.push(` }`);
|
|
3423
|
+
} else {
|
|
3424
|
+
lines.push(` ${info.varName} := in.${info.fieldName}`);
|
|
3425
|
+
lines.push(` if ${info.varName} == ${info.zeroLiteral} {`);
|
|
3426
|
+
lines.push(` ${info.varName} = ${info.goFallback}`);
|
|
3427
|
+
lines.push(` }`);
|
|
3428
|
+
}
|
|
3121
3429
|
}
|
|
3122
3430
|
if (propFallbackVars.size > 0)
|
|
3123
3431
|
lines.push("");
|
|
@@ -3197,7 +3505,7 @@ ${goFields.join(`
|
|
|
3197
3505
|
const fieldName = capitalizeFieldName(signal.getter);
|
|
3198
3506
|
if (propFieldNames.has(fieldName))
|
|
3199
3507
|
continue;
|
|
3200
|
-
const fallbackMatch = this.extractPropFallback(signal.initialValue);
|
|
3508
|
+
const fallbackMatch = this.extractPropFallback(signal.initialValue, signal.parsed);
|
|
3201
3509
|
const hoisted = fallbackMatch ? propFallbackVars.get(fallbackMatch.propName) : undefined;
|
|
3202
3510
|
if (hoisted) {
|
|
3203
3511
|
lines.push(` ${fieldName}: ${hoisted.varName},`);
|
|
@@ -4043,8 +4351,9 @@ ${goFields.join(`
|
|
|
4043
4351
|
for (const nested of findNestedComponents(ir.root)) {
|
|
4044
4352
|
localTaken.add(`${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`);
|
|
4045
4353
|
}
|
|
4354
|
+
const propTypeOverrides = buildPropTypeOverrides(this.emitCtx, ir);
|
|
4046
4355
|
for (const signal of ir.metadata.signals) {
|
|
4047
|
-
const match = this.extractPropFallback(signal.initialValue);
|
|
4356
|
+
const match = this.extractPropFallback(signal.initialValue, signal.parsed);
|
|
4048
4357
|
if (!match)
|
|
4049
4358
|
continue;
|
|
4050
4359
|
if (result.has(match.propName))
|
|
@@ -4055,6 +4364,8 @@ ${goFields.join(`
|
|
|
4055
4364
|
if (goPropDefault(param.defaultValue) !== null)
|
|
4056
4365
|
continue;
|
|
4057
4366
|
const fieldName = capitalizeFieldName(match.propName);
|
|
4367
|
+
const concreteType = propTypeOverrides.get(param.name) ?? typeInfoToGo(this.emitCtx, param.type, param.defaultValue);
|
|
4368
|
+
const nullishLowered = NULLISH_SCALAR_GO_TYPES.has(concreteType) && resolvePropGoType(this.emitCtx, param, propTypeOverrides) === "interface{}";
|
|
4058
4369
|
let zeroLiteral;
|
|
4059
4370
|
if (match.goFallback === "true" || match.goFallback === "false") {
|
|
4060
4371
|
zeroLiteral = "false";
|
|
@@ -4065,20 +4376,31 @@ ${goFields.join(`
|
|
|
4065
4376
|
} else {
|
|
4066
4377
|
continue;
|
|
4067
4378
|
}
|
|
4068
|
-
if (
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4379
|
+
if (!nullishLowered) {
|
|
4380
|
+
if (match.goFallback === zeroLiteral)
|
|
4381
|
+
continue;
|
|
4382
|
+
if (zeroLiteral === "0" && Number(match.goFallback) === 0)
|
|
4383
|
+
continue;
|
|
4384
|
+
}
|
|
4072
4385
|
let varName = match.propName;
|
|
4073
4386
|
while (localTaken.has(varName) || GO_KEYWORDS.has(varName)) {
|
|
4074
4387
|
varName += "_";
|
|
4075
4388
|
}
|
|
4076
4389
|
localTaken.add(varName);
|
|
4077
|
-
result.set(match.propName, {
|
|
4390
|
+
result.set(match.propName, {
|
|
4391
|
+
varName,
|
|
4392
|
+
fieldName,
|
|
4393
|
+
goFallback: match.goFallback,
|
|
4394
|
+
zeroLiteral,
|
|
4395
|
+
...nullishLowered ? { assertType: concreteType } : {}
|
|
4396
|
+
});
|
|
4078
4397
|
}
|
|
4079
4398
|
return result;
|
|
4080
4399
|
}
|
|
4081
|
-
extractPropFallback(initialValue) {
|
|
4400
|
+
extractPropFallback(initialValue, preParsed) {
|
|
4401
|
+
const structural = preParsed ? this.extractPropFallbackFromParsed(preParsed) : null;
|
|
4402
|
+
if (structural)
|
|
4403
|
+
return structural;
|
|
4082
4404
|
if (!this.state.propsObjectName)
|
|
4083
4405
|
return null;
|
|
4084
4406
|
const trimmed = initialValue.trim();
|
|
@@ -4092,9 +4414,38 @@ ${goFields.join(`
|
|
|
4092
4414
|
return null;
|
|
4093
4415
|
return { propName: m[1], goFallback };
|
|
4094
4416
|
}
|
|
4095
|
-
|
|
4096
|
-
if (
|
|
4417
|
+
extractPropFallbackFromParsed(preParsed) {
|
|
4418
|
+
if (preParsed.kind !== "logical" || preParsed.op !== "??")
|
|
4419
|
+
return null;
|
|
4420
|
+
const left = preParsed.left;
|
|
4421
|
+
const propName = left.kind === "identifier" && !this.state.propsObjectName ? left.name : left.kind === "member" && !left.computed && left.object.kind === "identifier" && left.object.name === this.state.propsObjectName ? left.property : null;
|
|
4422
|
+
if (!propName)
|
|
4423
|
+
return null;
|
|
4424
|
+
let right = preParsed.right;
|
|
4425
|
+
let negate = "";
|
|
4426
|
+
if (right.kind === "unary" && right.op === "-") {
|
|
4427
|
+
negate = "-";
|
|
4428
|
+
right = right.argument;
|
|
4429
|
+
}
|
|
4430
|
+
if (right.kind !== "literal")
|
|
4431
|
+
return null;
|
|
4432
|
+
if (negate && right.literalType !== "number")
|
|
4433
|
+
return null;
|
|
4434
|
+
if (right.literalType === "string") {
|
|
4435
|
+
return { propName, goFallback: JSON.stringify(right.value) };
|
|
4436
|
+
}
|
|
4437
|
+
const goFallback = goPropDefault(negate + (right.raw ?? String(right.value)));
|
|
4438
|
+
if (goFallback === null)
|
|
4439
|
+
return null;
|
|
4440
|
+
return { propName, goFallback };
|
|
4441
|
+
}
|
|
4442
|
+
extractPropNameFromInitialValue(initialValue, preParsed) {
|
|
4443
|
+
if (!this.state.propsObjectName) {
|
|
4444
|
+
if (preParsed?.kind === "logical" && (preParsed.op === "??" || preParsed.op === "||") && preParsed.left.kind === "identifier") {
|
|
4445
|
+
return preParsed.left.name;
|
|
4446
|
+
}
|
|
4097
4447
|
return null;
|
|
4448
|
+
}
|
|
4098
4449
|
const trimmed = initialValue.trim();
|
|
4099
4450
|
const name = this.state.propsObjectName;
|
|
4100
4451
|
const direct = new RegExp(`^${name}\\.(\\w+)(?:\\s*(?:\\?\\?|\\|\\|)\\s*.+)?$`);
|
|
@@ -4151,7 +4502,8 @@ ${goFields.join(`
|
|
|
4151
4502
|
renderElement(element) {
|
|
4152
4503
|
const tag = element.tag;
|
|
4153
4504
|
const attrs = this.renderAttributes(element);
|
|
4154
|
-
const
|
|
4505
|
+
const dangerousHtml = this.renderDangerousInnerHtml(element);
|
|
4506
|
+
const children = dangerousHtml !== null ? dangerousHtml : this.renderChildren(element.children);
|
|
4155
4507
|
let hydrationAttrs = "";
|
|
4156
4508
|
if (element.needsScope) {
|
|
4157
4509
|
hydrationAttrs += ` ${this.renderScopeMarker(".ScopeID")}`;
|
|
@@ -4186,6 +4538,22 @@ ${goFields.join(`
|
|
|
4186
4538
|
}
|
|
4187
4539
|
return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
|
|
4188
4540
|
}
|
|
4541
|
+
renderDangerousInnerHtml(element) {
|
|
4542
|
+
const resolution = resolveDangerousInnerHtml(element);
|
|
4543
|
+
if (!resolution)
|
|
4544
|
+
return null;
|
|
4545
|
+
if (resolution.kind === "dynamic") {
|
|
4546
|
+
this.errors.push(dangerousInnerHtmlDiagnostic(resolution.expr, resolution.loc));
|
|
4547
|
+
return "";
|
|
4548
|
+
}
|
|
4549
|
+
const violation = dangerousInnerHtmlMetacharViolation(resolution.html, this.name);
|
|
4550
|
+
if (violation) {
|
|
4551
|
+
const attr = element.attrs.find(isDangerousInnerHtmlAttr);
|
|
4552
|
+
this.errors.push(dangerousInnerHtmlDiagnostic(`{ __html: ${JSON.stringify(resolution.html)} }`, attr.loc, violation));
|
|
4553
|
+
return "";
|
|
4554
|
+
}
|
|
4555
|
+
return resolution.html;
|
|
4556
|
+
}
|
|
4189
4557
|
renderExpression(expr) {
|
|
4190
4558
|
if (expr.clientOnly) {
|
|
4191
4559
|
if (expr.slotId) {
|
|
@@ -4489,8 +4857,20 @@ ${goFields.join(`
|
|
|
4489
4857
|
const wrapRight = wrapIfMultiToken(emit(right));
|
|
4490
4858
|
if (op === "&&")
|
|
4491
4859
|
return `and ${wrapLeft} ${wrapRight}`;
|
|
4860
|
+
if (op === "??" && this.nillablePropNameOf(left) !== null) {
|
|
4861
|
+
return `bf_nullish ${wrapLeft} ${wrapRight}`;
|
|
4862
|
+
}
|
|
4492
4863
|
return `or ${wrapLeft} ${wrapRight}`;
|
|
4493
4864
|
}
|
|
4865
|
+
nillablePropNameOf(expr) {
|
|
4866
|
+
let name = null;
|
|
4867
|
+
if (expr.kind === "identifier") {
|
|
4868
|
+
name = expr.name;
|
|
4869
|
+
} else if (expr.kind === "member" && !expr.computed && expr.object.kind === "identifier" && expr.object.name === this.state.propsObjectName) {
|
|
4870
|
+
name = expr.property;
|
|
4871
|
+
}
|
|
4872
|
+
return name !== null && this.state.nullishConsumedPropNames.has(name) && this.state.nillablePropNames.has(name) ? name : null;
|
|
4873
|
+
}
|
|
4494
4874
|
conditional(test, consequent, alternate, emit) {
|
|
4495
4875
|
const t = emit(test);
|
|
4496
4876
|
const c = this.renderConditionalBranch(consequent);
|
|
@@ -4919,8 +5299,8 @@ ${goFields.join(`
|
|
|
4919
5299
|
const value = negated ? "false" : "true";
|
|
4920
5300
|
return `len (bf_filter ${arrayExpr} "${field}" ${value})`;
|
|
4921
5301
|
}
|
|
4922
|
-
renderPredicateCondition(pred, param) {
|
|
4923
|
-
return this.renderFilterExpr(pred, param);
|
|
5302
|
+
renderPredicateCondition(pred, param, datumField) {
|
|
5303
|
+
return this.renderFilterExpr(pred, param, new Map, datumField ?? undefined);
|
|
4924
5304
|
}
|
|
4925
5305
|
needsParens(expr) {
|
|
4926
5306
|
return expr.kind === "logical" || expr.kind === "unary" || expr.kind === "conditional";
|
|
@@ -4941,21 +5321,23 @@ ${goFields.join(`
|
|
|
4941
5321
|
}
|
|
4942
5322
|
return null;
|
|
4943
5323
|
}
|
|
4944
|
-
renderFilterExpr(expr, param, localVarMap = new Map) {
|
|
5324
|
+
renderFilterExpr(expr, param, localVarMap = new Map, datumField) {
|
|
4945
5325
|
if (this.filterExprDepth === 0)
|
|
4946
5326
|
this.filterExprUnsupported = false;
|
|
4947
5327
|
this.filterExprDepth++;
|
|
4948
5328
|
try {
|
|
4949
|
-
return this.renderFilterExprNode(expr, param, localVarMap);
|
|
5329
|
+
return this.renderFilterExprNode(expr, param, localVarMap, datumField);
|
|
4950
5330
|
} finally {
|
|
4951
5331
|
this.filterExprDepth--;
|
|
4952
5332
|
}
|
|
4953
5333
|
}
|
|
4954
|
-
renderFilterExprNode(expr, param, localVarMap) {
|
|
5334
|
+
renderFilterExprNode(expr, param, localVarMap, datumField) {
|
|
5335
|
+
const paramPrefix = datumField ? `.${datumField}` : "";
|
|
5336
|
+
const paramDot = paramPrefix || ".";
|
|
4955
5337
|
switch (expr.kind) {
|
|
4956
5338
|
case "identifier": {
|
|
4957
5339
|
if (expr.name === param) {
|
|
4958
|
-
return
|
|
5340
|
+
return paramDot;
|
|
4959
5341
|
}
|
|
4960
5342
|
const signal = localVarMap.get(expr.name);
|
|
4961
5343
|
if (signal) {
|
|
@@ -4973,24 +5355,24 @@ ${goFields.join(`
|
|
|
4973
5355
|
return String(expr.value);
|
|
4974
5356
|
case "member": {
|
|
4975
5357
|
if (expr.object.kind === "identifier" && expr.object.name === param) {
|
|
4976
|
-
return
|
|
5358
|
+
return `${paramPrefix}.${capitalizeFieldName(expr.property)}`;
|
|
4977
5359
|
}
|
|
4978
5360
|
if (expr.property === "length") {
|
|
4979
5361
|
const innerHO = this.higherOrderShapeOf(expr.object);
|
|
4980
5362
|
if (innerHO && innerHO.method === "filter") {
|
|
4981
|
-
const lenExpr = this.renderFilterLengthExpr(innerHO, (e) => this.renderFilterExpr(e, param, localVarMap));
|
|
5363
|
+
const lenExpr = this.renderFilterLengthExpr(innerHO, (e) => this.renderFilterExpr(e, param, localVarMap, datumField));
|
|
4982
5364
|
if (lenExpr)
|
|
4983
5365
|
return `(${lenExpr})`;
|
|
4984
5366
|
}
|
|
4985
5367
|
}
|
|
4986
|
-
const obj = this.renderFilterExpr(expr.object, param, localVarMap);
|
|
5368
|
+
const obj = this.renderFilterExpr(expr.object, param, localVarMap, datumField);
|
|
4987
5369
|
if (this.filterExprUnsupported)
|
|
4988
5370
|
return "false";
|
|
4989
5371
|
return `${obj}.${capitalizeFieldName(expr.property)}`;
|
|
4990
5372
|
}
|
|
4991
5373
|
case "call": {
|
|
4992
5374
|
if (expr.callee.kind === "member" && expr.callee.object.kind === "identifier" && expr.callee.object.name === param) {
|
|
4993
|
-
return
|
|
5375
|
+
return `${paramPrefix}.${capitalizeFieldName(expr.callee.property)}`;
|
|
4994
5376
|
}
|
|
4995
5377
|
if (expr.callee.kind === "identifier" && expr.args.length === 0) {
|
|
4996
5378
|
return `$.${capitalizeFieldName(expr.callee.name)}`;
|
|
@@ -4998,13 +5380,13 @@ ${goFields.join(`
|
|
|
4998
5380
|
if (asCallbackMethodCall3(expr) !== null) {
|
|
4999
5381
|
return this.refuseFilterExprNode(expr);
|
|
5000
5382
|
}
|
|
5001
|
-
const result = this.renderFilterExpr(expr.callee, param, localVarMap);
|
|
5383
|
+
const result = this.renderFilterExpr(expr.callee, param, localVarMap, datumField);
|
|
5002
5384
|
if (this.filterExprUnsupported)
|
|
5003
5385
|
return "false";
|
|
5004
5386
|
return result;
|
|
5005
5387
|
}
|
|
5006
5388
|
case "unary": {
|
|
5007
|
-
const arg = this.renderFilterExpr(expr.argument, param, localVarMap);
|
|
5389
|
+
const arg = this.renderFilterExpr(expr.argument, param, localVarMap, datumField);
|
|
5008
5390
|
if (this.filterExprUnsupported)
|
|
5009
5391
|
return "false";
|
|
5010
5392
|
if (expr.op === "!") {
|
|
@@ -5017,10 +5399,10 @@ ${goFields.join(`
|
|
|
5017
5399
|
return arg;
|
|
5018
5400
|
}
|
|
5019
5401
|
case "binary": {
|
|
5020
|
-
const left = this.renderFilterExpr(expr.left, param, localVarMap);
|
|
5402
|
+
const left = this.renderFilterExpr(expr.left, param, localVarMap, datumField);
|
|
5021
5403
|
if (this.filterExprUnsupported)
|
|
5022
5404
|
return "false";
|
|
5023
|
-
const right = this.renderFilterExpr(expr.right, param, localVarMap);
|
|
5405
|
+
const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField);
|
|
5024
5406
|
if (this.filterExprUnsupported)
|
|
5025
5407
|
return "false";
|
|
5026
5408
|
switch (expr.op) {
|
|
@@ -5051,10 +5433,10 @@ ${goFields.join(`
|
|
|
5051
5433
|
}
|
|
5052
5434
|
}
|
|
5053
5435
|
case "logical": {
|
|
5054
|
-
const left = this.renderFilterExpr(expr.left, param, localVarMap);
|
|
5436
|
+
const left = this.renderFilterExpr(expr.left, param, localVarMap, datumField);
|
|
5055
5437
|
if (this.filterExprUnsupported)
|
|
5056
5438
|
return "false";
|
|
5057
|
-
const right = this.renderFilterExpr(expr.right, param, localVarMap);
|
|
5439
|
+
const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField);
|
|
5058
5440
|
if (this.filterExprUnsupported)
|
|
5059
5441
|
return "false";
|
|
5060
5442
|
if (expr.op === "&&") {
|
|
@@ -5122,11 +5504,22 @@ ${goFields.join(`
|
|
|
5122
5504
|
if (trimmed === "null" || trimmed === "undefined") {
|
|
5123
5505
|
return '""';
|
|
5124
5506
|
}
|
|
5507
|
+
if (this.staticLoopItemStack.length > 0) {
|
|
5508
|
+
const top = this.staticLoopItemStack[this.staticLoopItemStack.length - 1];
|
|
5509
|
+
const parsedForBake = preParsed ?? parseExpression4(trimmed);
|
|
5510
|
+
const resolved = evaluateStaticLiteral3(parsedForBake, new Map([[top.param, top.item]]));
|
|
5511
|
+
const literal = resolved !== null ? scalarToGoLiteral(resolved.value) : null;
|
|
5512
|
+
if (literal !== null) {
|
|
5513
|
+
return literal;
|
|
5514
|
+
}
|
|
5515
|
+
this.staticLoopBakeFailed = true;
|
|
5516
|
+
return '""';
|
|
5517
|
+
}
|
|
5125
5518
|
const staticIndexed = this.resolveStaticRecordLiteralIndex(trimmed);
|
|
5126
5519
|
if (staticIndexed !== null) {
|
|
5127
5520
|
return staticIndexed;
|
|
5128
5521
|
}
|
|
5129
|
-
if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
|
|
5522
|
+
if (!this.isLoopShadowedName(trimmed) && /^[A-Za-z_$][\w$]*$/.test(trimmed)) {
|
|
5130
5523
|
const litConst = (this.state.localConstants ?? []).find((c) => c.name === trimmed);
|
|
5131
5524
|
if (litConst?.value !== undefined) {
|
|
5132
5525
|
const v = litConst.value.trim();
|
|
@@ -5144,7 +5537,7 @@ ${goFields.join(`
|
|
|
5144
5537
|
if (inlined !== null) {
|
|
5145
5538
|
return this.convertExpressionToGo(stringifyParsedExpr2(inlined), out, inlined);
|
|
5146
5539
|
}
|
|
5147
|
-
const parsed = preParsed ??
|
|
5540
|
+
const parsed = preParsed ?? parseExpression4(trimmed);
|
|
5148
5541
|
const support = isSupported(parsed);
|
|
5149
5542
|
if (!support.supported) {
|
|
5150
5543
|
this.state.errors.push({
|
|
@@ -5162,10 +5555,15 @@ ${goFields.join(`
|
|
|
5162
5555
|
out.parsed = parsed;
|
|
5163
5556
|
return this.renderParsedExpr(parsed);
|
|
5164
5557
|
}
|
|
5558
|
+
isLoopShadowedName(name) {
|
|
5559
|
+
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));
|
|
5560
|
+
}
|
|
5165
5561
|
resolveStaticRecordLiteralIndex(jsExpr) {
|
|
5166
5562
|
const m = /^([A-Za-z_$][\w$]*)\[\s*(?:'([^']*)'|"([^"]*)")\s*\]$/.exec(jsExpr) ?? /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/.exec(jsExpr);
|
|
5167
5563
|
if (!m)
|
|
5168
5564
|
return null;
|
|
5565
|
+
if (this.isLoopShadowedName(m[1]))
|
|
5566
|
+
return null;
|
|
5169
5567
|
const key = m[2] ?? m[3];
|
|
5170
5568
|
const constInfo = (this.state.localConstants ?? []).find((c) => c.name === m[1] && c.isModule);
|
|
5171
5569
|
if (constInfo?.value === undefined)
|
|
@@ -5271,7 +5669,7 @@ ${goFields.join(`
|
|
|
5271
5669
|
}
|
|
5272
5670
|
convertConditionToGo(jsCondition, preParsed) {
|
|
5273
5671
|
const trimmed = jsCondition.trim();
|
|
5274
|
-
const parsed = preParsed ??
|
|
5672
|
+
const parsed = preParsed ?? parseExpression4(trimmed);
|
|
5275
5673
|
const support = isSupported(parsed);
|
|
5276
5674
|
if (!support.supported) {
|
|
5277
5675
|
this.state.errors.push({
|
|
@@ -5492,6 +5890,29 @@ ${goFields.join(`
|
|
|
5492
5890
|
}
|
|
5493
5891
|
return;
|
|
5494
5892
|
}
|
|
5893
|
+
wrapperDatumField(loop) {
|
|
5894
|
+
if (!loop.childComponent)
|
|
5895
|
+
return null;
|
|
5896
|
+
for (const prop of loop.childComponent.props) {
|
|
5897
|
+
if (prop.isEventHandler)
|
|
5898
|
+
continue;
|
|
5899
|
+
if (prop.value.kind !== "expression")
|
|
5900
|
+
continue;
|
|
5901
|
+
const parsed = prop.value.parsed;
|
|
5902
|
+
const isBareParamRef = parsed ? parsed.kind === "identifier" && parsed.name === loop.param : prop.value.expr.trim() === loop.param;
|
|
5903
|
+
if (isBareParamRef)
|
|
5904
|
+
return capitalizeFieldName(prop.name);
|
|
5905
|
+
}
|
|
5906
|
+
return null;
|
|
5907
|
+
}
|
|
5908
|
+
getBakedStaticChildLoop(markerId, childComponent, arrayParsed, param, key) {
|
|
5909
|
+
if (this.bakedStaticChildLoopCache.has(markerId)) {
|
|
5910
|
+
return this.bakedStaticChildLoopCache.get(markerId) ?? null;
|
|
5911
|
+
}
|
|
5912
|
+
const result = analyzeBakeableStaticChildLoop({ props: childComponent.props, loopArrayParsed: arrayParsed, loopParam: param, loopKey: key }, this.state.localConstants, { isNameShadowed: (name) => this.state.staticLoopSourceBoundNames.has(name) });
|
|
5913
|
+
this.bakedStaticChildLoopCache.set(markerId, result);
|
|
5914
|
+
return result;
|
|
5915
|
+
}
|
|
5495
5916
|
renderLoop(loop) {
|
|
5496
5917
|
if (loop.clientOnly) {
|
|
5497
5918
|
return `{{bfComment "loop:${loop.markerId}"}}{{bfComment "/loop:${loop.markerId}"}}`;
|
|
@@ -5512,8 +5933,13 @@ ${goFields.join(`
|
|
|
5512
5933
|
}
|
|
5513
5934
|
});
|
|
5514
5935
|
}
|
|
5936
|
+
const bakedChildLoop = loop.childComponent ? this.getBakedStaticChildLoop(loop.markerId, loop.childComponent, loop.arrayParsed, loop.param, loop.key ?? undefined) : null;
|
|
5937
|
+
const bakedElementLoop = loop.childComponent ? null : analyzeBakeableStaticElementLoop(loop, this.state.localConstants, { isNameShadowed: (name) => this.state.staticLoopSourceBoundNames.has(name) });
|
|
5938
|
+
if (bakedElementLoop) {
|
|
5939
|
+
return this.renderUnrolledStaticElementLoop(loop, bakedElementLoop.items);
|
|
5940
|
+
}
|
|
5515
5941
|
const arrayName = loop.array.trim();
|
|
5516
|
-
if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
|
|
5942
|
+
if (bakedChildLoop === null && /^[A-Za-z_$][\w$]*$/.test(arrayName)) {
|
|
5517
5943
|
const arrayConst = this.state.localConstants.find((c) => c.name === arrayName);
|
|
5518
5944
|
if (arrayConst && !arrayConst.isModule && arrayConst.parsed && !this.isStringExpr(arrayConst.parsed, new Set)) {
|
|
5519
5945
|
this.state.errors.push({
|
|
@@ -5527,7 +5953,7 @@ ${goFields.join(`
|
|
|
5527
5953
|
});
|
|
5528
5954
|
}
|
|
5529
5955
|
}
|
|
5530
|
-
let goArray = this.convertExpressionToGo(loop.array);
|
|
5956
|
+
let goArray = loop.childComponent ? "" : this.convertExpressionToGo(loop.array);
|
|
5531
5957
|
const param = loop.param;
|
|
5532
5958
|
let index = loop.index || "_";
|
|
5533
5959
|
let rangeIndex = index;
|
|
@@ -5595,7 +6021,8 @@ ${goFields.join(`
|
|
|
5595
6021
|
if (loop.filterPredicate) {
|
|
5596
6022
|
let filterCond;
|
|
5597
6023
|
if (loop.filterPredicate.predicate) {
|
|
5598
|
-
|
|
6024
|
+
const datumField = this.wrapperDatumField(loop);
|
|
6025
|
+
filterCond = this.renderPredicateCondition(loop.filterPredicate.predicate, loop.filterPredicate.param, datumField);
|
|
5599
6026
|
} else {
|
|
5600
6027
|
filterCond = "true";
|
|
5601
6028
|
}
|
|
@@ -5603,6 +6030,38 @@ ${goFields.join(`
|
|
|
5603
6030
|
}
|
|
5604
6031
|
return `{{bfComment "loop:${loop.markerId}"}}{{range $${rangeIndex}, $${rangeValue} := ${goArray}}}${itemMarker}${children}{{end}}{{bfComment "/loop:${loop.markerId}"}}`;
|
|
5605
6032
|
}
|
|
6033
|
+
renderUnrolledStaticElementLoop(loop, items) {
|
|
6034
|
+
this.inLoop = true;
|
|
6035
|
+
this.loopWrapperStack.push(false);
|
|
6036
|
+
this.loopKeyDepthStack.push(loop.depth);
|
|
6037
|
+
this.loopScalarItemStack.push(this.scalarLiteralLoopGoType(loop.arrayParsed, loop.itemType) !== null);
|
|
6038
|
+
this.loopParamStack.push(loop.param);
|
|
6039
|
+
let body = "";
|
|
6040
|
+
for (const item of items) {
|
|
6041
|
+
this.staticLoopItemStack.push({ param: loop.param, item });
|
|
6042
|
+
body += this.renderChildren(loop.children);
|
|
6043
|
+
this.staticLoopItemStack.pop();
|
|
6044
|
+
if (this.staticLoopBakeFailed) {
|
|
6045
|
+
this.staticLoopBakeFailed = false;
|
|
6046
|
+
this.state.errors.push({
|
|
6047
|
+
code: "BF101",
|
|
6048
|
+
severity: "error",
|
|
6049
|
+
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.`,
|
|
6050
|
+
loc: loop.loc ?? this.makeLoc(),
|
|
6051
|
+
suggestion: {
|
|
6052
|
+
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."
|
|
6053
|
+
}
|
|
6054
|
+
});
|
|
6055
|
+
break;
|
|
6056
|
+
}
|
|
6057
|
+
}
|
|
6058
|
+
this.loopParamStack.pop();
|
|
6059
|
+
this.loopScalarItemStack.pop();
|
|
6060
|
+
this.loopKeyDepthStack.pop();
|
|
6061
|
+
this.loopWrapperStack.pop();
|
|
6062
|
+
this.inLoop = false;
|
|
6063
|
+
return `{{bfComment "loop:${loop.markerId}"}}${body}{{bfComment "/loop:${loop.markerId}"}}`;
|
|
6064
|
+
}
|
|
5606
6065
|
loopItemMarker(loop) {
|
|
5607
6066
|
if (loop.bodyIsMultiRoot)
|
|
5608
6067
|
return `{{bfComment "bf-loop-i"}}`;
|
|
@@ -5717,12 +6176,12 @@ ${children}`;
|
|
|
5717
6176
|
if (css !== null)
|
|
5718
6177
|
return `style="${css}"`;
|
|
5719
6178
|
}
|
|
5720
|
-
if (
|
|
6179
|
+
if (isBooleanAttr2(name) || value.presenceOrUndefined) {
|
|
5721
6180
|
const { condition: goCond, preamble } = this.convertConditionToGo(value.expr, value.parsed);
|
|
5722
6181
|
const body = name.startsWith("aria-") ? `${name}="true"` : name;
|
|
5723
6182
|
return `${preamble}{{if ${goCond}}}${body}{{end}}`;
|
|
5724
6183
|
}
|
|
5725
|
-
const parsed = value.parsed ??
|
|
6184
|
+
const parsed = value.parsed ?? parseExpression4(value.expr.trim());
|
|
5726
6185
|
if (parsed.kind === "conditional") {
|
|
5727
6186
|
const undef = (e) => e.kind === "identifier" && (e.name === "undefined" || e.name === "null") || e.kind === "literal" && (e.value === null || e.value === undefined);
|
|
5728
6187
|
const test = parsed.test;
|
|
@@ -5787,7 +6246,7 @@ ${children}`;
|
|
|
5787
6246
|
if (!entries)
|
|
5788
6247
|
return null;
|
|
5789
6248
|
for (const e of entries) {
|
|
5790
|
-
if (e.kind === "expr" && !isSupported(
|
|
6249
|
+
if (e.kind === "expr" && !isSupported(parseExpression4(e.expr)).supported)
|
|
5791
6250
|
return null;
|
|
5792
6251
|
}
|
|
5793
6252
|
return entries.map((e) => e.kind === "literal" ? `${this.escapeAttrText(e.cssKey)}:${this.escapeAttrText(e.value)}` : `${this.escapeAttrText(e.cssKey)}:{{${this.convertExpressionToGo(e.expr)}}}`).join(";");
|
|
@@ -5797,6 +6256,8 @@ ${children}`;
|
|
|
5797
6256
|
for (const attr of element.attrs) {
|
|
5798
6257
|
if (attr.clientOnly)
|
|
5799
6258
|
continue;
|
|
6259
|
+
if (isDangerousInnerHtmlAttr(attr))
|
|
6260
|
+
continue;
|
|
5800
6261
|
let attrName;
|
|
5801
6262
|
if (attr.name === "className")
|
|
5802
6263
|
attrName = "class";
|