@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/index.js
CHANGED
|
@@ -24,8 +24,8 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
24
24
|
// src/adapter/go-template-adapter.ts
|
|
25
25
|
import {
|
|
26
26
|
BaseAdapter,
|
|
27
|
-
isBooleanAttr,
|
|
28
|
-
parseExpression as
|
|
27
|
+
isBooleanAttr as isBooleanAttr2,
|
|
28
|
+
parseExpression as parseExpression4,
|
|
29
29
|
stringifyParsedExpr as stringifyParsedExpr2,
|
|
30
30
|
parseStyleObjectEntries,
|
|
31
31
|
isSupported,
|
|
@@ -43,7 +43,13 @@ import {
|
|
|
43
43
|
prepareLoweringMatchers,
|
|
44
44
|
envSignalReaderFor,
|
|
45
45
|
computeSsrSeedPlan,
|
|
46
|
-
isStringConcatBinary
|
|
46
|
+
isStringConcatBinary,
|
|
47
|
+
isDangerousInnerHtmlAttr,
|
|
48
|
+
resolveDangerousInnerHtml,
|
|
49
|
+
dangerousInnerHtmlMetacharViolation,
|
|
50
|
+
dangerousInnerHtmlDiagnostic,
|
|
51
|
+
collectLoopBoundNames as collectLoopBoundNames2,
|
|
52
|
+
evaluateStaticLiteral as evaluateStaticLiteral3
|
|
47
53
|
} from "@barefootjs/jsx";
|
|
48
54
|
import { findInterpolationEnd } from "@barefootjs/jsx/scanner";
|
|
49
55
|
import { BF_REGION, escapeHtml } from "@barefootjs/shared";
|
|
@@ -349,6 +355,7 @@ class CompileState {
|
|
|
349
355
|
restPropsName = null;
|
|
350
356
|
moduleStringConsts = new Map;
|
|
351
357
|
localConstants = [];
|
|
358
|
+
staticLoopSourceBoundNames = new Set;
|
|
352
359
|
localHelperNames = new Set;
|
|
353
360
|
currentMemos = [];
|
|
354
361
|
currentTypeDefinitions = [];
|
|
@@ -359,6 +366,8 @@ class CompileState {
|
|
|
359
366
|
hoistedMemoLocals = new Map;
|
|
360
367
|
loweringMatchers = [];
|
|
361
368
|
nillablePropNames = new Set;
|
|
369
|
+
nullishConsumedPropNames = new Set;
|
|
370
|
+
omittableAttrConsumedPropNames = new Set;
|
|
362
371
|
stringValueNames = new Set;
|
|
363
372
|
rootScopeNodes = new Set;
|
|
364
373
|
memoBackedLoopSlice = new Map;
|
|
@@ -517,6 +526,178 @@ function collectNestedComponents(node, result) {
|
|
|
517
526
|
}
|
|
518
527
|
}
|
|
519
528
|
|
|
529
|
+
// src/adapter/analysis/static-child-loop-bake.ts
|
|
530
|
+
import { evaluateStaticLiteral, parseExpression, resolveStaticLoopSource } from "@barefootjs/jsx";
|
|
531
|
+
function scalarToGoLiteral(value) {
|
|
532
|
+
if (typeof value === "string")
|
|
533
|
+
return `"${escapeGoString(value)}"`;
|
|
534
|
+
if (typeof value === "number")
|
|
535
|
+
return String(value);
|
|
536
|
+
if (typeof value === "boolean")
|
|
537
|
+
return value ? "true" : "false";
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
function analyzeBakeableStaticChildLoop(nested, localConstants, opts) {
|
|
541
|
+
if (!nested.loopParam || /^[{[]/.test(nested.loopParam))
|
|
542
|
+
return null;
|
|
543
|
+
const staticItemsResult = resolveStaticLoopSource(nested.loopArrayParsed, localConstants, opts);
|
|
544
|
+
if (staticItemsResult === null)
|
|
545
|
+
return null;
|
|
546
|
+
const items = [];
|
|
547
|
+
for (const item of staticItemsResult) {
|
|
548
|
+
const bindings = new Map([[nested.loopParam, item]]);
|
|
549
|
+
const inputFields = [];
|
|
550
|
+
for (const prop of nested.props) {
|
|
551
|
+
if (prop.isEventHandler)
|
|
552
|
+
continue;
|
|
553
|
+
if (prop.name.includes("-"))
|
|
554
|
+
continue;
|
|
555
|
+
const resolved = resolvePropValue(prop.value, bindings);
|
|
556
|
+
if (resolved === undefined)
|
|
557
|
+
return null;
|
|
558
|
+
const goValue = scalarToGoLiteral(resolved);
|
|
559
|
+
if (goValue === null)
|
|
560
|
+
return null;
|
|
561
|
+
inputFields.push({ goField: capitalizeFieldName(prop.name), goValue });
|
|
562
|
+
}
|
|
563
|
+
let dataKey = null;
|
|
564
|
+
if (nested.loopKey) {
|
|
565
|
+
const keyExpr = parseExpression(nested.loopKey);
|
|
566
|
+
const keyResolved = evaluateStaticLiteral(keyExpr, bindings);
|
|
567
|
+
if (keyResolved === null)
|
|
568
|
+
return null;
|
|
569
|
+
dataKey = String(keyResolved.value);
|
|
570
|
+
}
|
|
571
|
+
items.push({ inputFields, dataKey });
|
|
572
|
+
}
|
|
573
|
+
return { items };
|
|
574
|
+
}
|
|
575
|
+
function resolvePropValue(value, bindings) {
|
|
576
|
+
switch (value.kind) {
|
|
577
|
+
case "literal":
|
|
578
|
+
return value.value;
|
|
579
|
+
case "boolean-shorthand":
|
|
580
|
+
case "boolean-attr":
|
|
581
|
+
return true;
|
|
582
|
+
case "expression": {
|
|
583
|
+
if (!value.parsed)
|
|
584
|
+
return;
|
|
585
|
+
const resolved = evaluateStaticLiteral(value.parsed, bindings);
|
|
586
|
+
return resolved === null ? undefined : resolved.value;
|
|
587
|
+
}
|
|
588
|
+
default:
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/adapter/analysis/static-element-loop-bake.ts
|
|
594
|
+
import {
|
|
595
|
+
evaluateStaticLiteral as evaluateStaticLiteral2,
|
|
596
|
+
resolveStaticLoopSource as resolveStaticLoopSource2
|
|
597
|
+
} from "@barefootjs/jsx";
|
|
598
|
+
var ALLOWED_ATTR_EXPRESSION_KINDS = new Set([
|
|
599
|
+
"identifier",
|
|
600
|
+
"member",
|
|
601
|
+
"index-access",
|
|
602
|
+
"literal"
|
|
603
|
+
]);
|
|
604
|
+
function analyzeBakeableStaticElementLoop(loop, localConstants, opts) {
|
|
605
|
+
if (loop.childComponent)
|
|
606
|
+
return null;
|
|
607
|
+
if (loop.method === "flatMap" || loop.flatMapCallback)
|
|
608
|
+
return null;
|
|
609
|
+
if (!loop.param || /^[{[]/.test(loop.param))
|
|
610
|
+
return null;
|
|
611
|
+
if (loop.index && loop.index !== "_")
|
|
612
|
+
return null;
|
|
613
|
+
if (loop.paramBindings && loop.paramBindings.length > 0)
|
|
614
|
+
return null;
|
|
615
|
+
if (loop.filterPredicate || loop.sortComparator)
|
|
616
|
+
return null;
|
|
617
|
+
if (loop.iterationShape || loop.objectIteration)
|
|
618
|
+
return null;
|
|
619
|
+
if (loop.bodyIsMultiRoot || loop.bodyIsItemConditional)
|
|
620
|
+
return null;
|
|
621
|
+
if (!isFoldableTree(loop.children))
|
|
622
|
+
return null;
|
|
623
|
+
const items = resolveStaticLoopSource2(loop.arrayParsed, localConstants, opts);
|
|
624
|
+
if (items === null)
|
|
625
|
+
return null;
|
|
626
|
+
for (const item of items) {
|
|
627
|
+
const bindings = new Map([[loop.param, item]]);
|
|
628
|
+
if (!allExpressionsFoldFor(loop.children, bindings))
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
return { items };
|
|
632
|
+
}
|
|
633
|
+
function isFoldableTree(nodes) {
|
|
634
|
+
for (const node of nodes) {
|
|
635
|
+
switch (node.type) {
|
|
636
|
+
case "text":
|
|
637
|
+
case "expression":
|
|
638
|
+
continue;
|
|
639
|
+
case "element":
|
|
640
|
+
if (!isFoldableAttrs(node))
|
|
641
|
+
return false;
|
|
642
|
+
if (!isFoldableTree(node.children))
|
|
643
|
+
return false;
|
|
644
|
+
continue;
|
|
645
|
+
default:
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
function isFoldableAttrs(element) {
|
|
652
|
+
for (const attr of element.attrs) {
|
|
653
|
+
if (attr.clientOnly)
|
|
654
|
+
continue;
|
|
655
|
+
switch (attr.value.kind) {
|
|
656
|
+
case "literal":
|
|
657
|
+
case "boolean-attr":
|
|
658
|
+
case "boolean-shorthand":
|
|
659
|
+
continue;
|
|
660
|
+
case "expression":
|
|
661
|
+
if (!attr.value.parsed || !ALLOWED_ATTR_EXPRESSION_KINDS.has(attr.value.parsed.kind))
|
|
662
|
+
return false;
|
|
663
|
+
continue;
|
|
664
|
+
default:
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return true;
|
|
669
|
+
}
|
|
670
|
+
function allExpressionsFoldFor(nodes, bindings) {
|
|
671
|
+
for (const node of nodes) {
|
|
672
|
+
if (node.type === "expression") {
|
|
673
|
+
if (node.clientOnly)
|
|
674
|
+
continue;
|
|
675
|
+
if (!node.parsed || !resolvesToScalar(node.parsed, bindings))
|
|
676
|
+
return false;
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
if (node.type === "element") {
|
|
680
|
+
for (const attr of node.attrs) {
|
|
681
|
+
if (attr.clientOnly)
|
|
682
|
+
continue;
|
|
683
|
+
if (attr.value.kind !== "expression")
|
|
684
|
+
continue;
|
|
685
|
+
if (!attr.value.parsed || !resolvesToScalar(attr.value.parsed, bindings))
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
if (!allExpressionsFoldFor(node.children, bindings))
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return true;
|
|
693
|
+
}
|
|
694
|
+
function resolvesToScalar(expr, bindings) {
|
|
695
|
+
const resolved = evaluateStaticLiteral2(expr, bindings);
|
|
696
|
+
if (resolved === null)
|
|
697
|
+
return false;
|
|
698
|
+
return scalarToGoLiteral(resolved.value) !== null;
|
|
699
|
+
}
|
|
700
|
+
|
|
520
701
|
// src/adapter/expr/helper-inline.ts
|
|
521
702
|
function inlineLocalHelperCall(ctx, jsExpr, callParsed) {
|
|
522
703
|
if (ctx.state.localHelperNames.size === 0)
|
|
@@ -704,7 +885,7 @@ function forEachValueChild(n, visit) {
|
|
|
704
885
|
|
|
705
886
|
// src/adapter/expr/url-builder.ts
|
|
706
887
|
import {
|
|
707
|
-
parseExpression,
|
|
888
|
+
parseExpression as parseExpression2,
|
|
708
889
|
stringifyParsedExpr,
|
|
709
890
|
isValidHelperId
|
|
710
891
|
} from "@barefootjs/jsx";
|
|
@@ -737,7 +918,7 @@ function lowerRegisteredCall(ctx, jsExpr, preParsed) {
|
|
|
737
918
|
if (!call) {
|
|
738
919
|
if (!/^\s*[A-Za-z_$][\w$]*\s*\(/.test(jsExpr))
|
|
739
920
|
return null;
|
|
740
|
-
const parsed =
|
|
921
|
+
const parsed = parseExpression2(jsExpr);
|
|
741
922
|
if (parsed.kind !== "call")
|
|
742
923
|
return null;
|
|
743
924
|
call = parsed;
|
|
@@ -936,7 +1117,7 @@ function convertInitialValue(ctx, value, typeInfo, propsParams, preParsed) {
|
|
|
936
1117
|
return `in.${capitalizeFieldName(value)}`;
|
|
937
1118
|
}
|
|
938
1119
|
}
|
|
939
|
-
const propName = ctx.extractPropNameFromInitialValue(value);
|
|
1120
|
+
const propName = ctx.extractPropNameFromInitialValue(value, preParsed);
|
|
940
1121
|
if (propName && propsParams?.some((p) => p.name === propName)) {
|
|
941
1122
|
return `in.${capitalizeFieldName(propName)}`;
|
|
942
1123
|
}
|
|
@@ -1831,7 +2012,7 @@ function propsAccessNameFromParsed2(ctx, node) {
|
|
|
1831
2012
|
|
|
1832
2013
|
// src/adapter/spread/spread-codegen.ts
|
|
1833
2014
|
import ts2 from "typescript";
|
|
1834
|
-
import { parseExpression as
|
|
2015
|
+
import { parseExpression as parseExpression3, parseRecordIndexAccess } from "@barefootjs/jsx";
|
|
1835
2016
|
function collectSpreadSlots(ctx, node) {
|
|
1836
2017
|
const result = [];
|
|
1837
2018
|
collectSpreadSlotsRecursive(ctx, node, result);
|
|
@@ -1938,7 +2119,7 @@ function parsedObjectLiteralToGoMap(parsed) {
|
|
|
1938
2119
|
}
|
|
1939
2120
|
function buildSpreadInitializer(ctx, spreadExpr, ir, parsed) {
|
|
1940
2121
|
const trimmed = spreadExpr.trim();
|
|
1941
|
-
const conditionalTree = parsed ??
|
|
2122
|
+
const conditionalTree = parsed ?? parseExpression3(trimmed);
|
|
1942
2123
|
const conditional = buildConditionalSpreadInitializer(ctx, conditionalTree, ir);
|
|
1943
2124
|
if (conditional !== undefined)
|
|
1944
2125
|
return conditional;
|
|
@@ -1969,7 +2150,7 @@ function buildSpreadInitializer(ctx, spreadExpr, ir, parsed) {
|
|
|
1969
2150
|
if (localConst?.value !== undefined) {
|
|
1970
2151
|
const initTrimmed = localConst.value.trim();
|
|
1971
2152
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(initTrimmed)) {
|
|
1972
|
-
const resolved = buildConditionalSpreadInitializer(ctx,
|
|
2153
|
+
const resolved = buildConditionalSpreadInitializer(ctx, parseExpression3(initTrimmed), ir);
|
|
1973
2154
|
if (resolved)
|
|
1974
2155
|
return resolved;
|
|
1975
2156
|
if (resolved === null)
|
|
@@ -2080,11 +2261,12 @@ function recordIndexAccessToGoMap(ctx, val, ir) {
|
|
|
2080
2261
|
}
|
|
2081
2262
|
|
|
2082
2263
|
// src/adapter/props/prop-types.ts
|
|
2264
|
+
import { isBooleanAttr } from "@barefootjs/jsx";
|
|
2083
2265
|
function buildPropTypeOverrides(ctx, ir) {
|
|
2084
2266
|
const overrides = new Map;
|
|
2085
2267
|
for (const signal of ir.metadata.signals) {
|
|
2086
2268
|
const propNames = [signal.initialValue];
|
|
2087
|
-
const extracted = ctx.extractPropNameFromInitialValue(signal.initialValue);
|
|
2269
|
+
const extracted = ctx.extractPropNameFromInitialValue(signal.initialValue, signal.parsed);
|
|
2088
2270
|
if (extracted)
|
|
2089
2271
|
propNames.push(extracted);
|
|
2090
2272
|
for (const propName of propNames) {
|
|
@@ -2141,11 +2323,96 @@ function collectToFixedPropNames(root) {
|
|
|
2141
2323
|
walk(root);
|
|
2142
2324
|
return names;
|
|
2143
2325
|
}
|
|
2326
|
+
var NULLISH_SCALAR_GO_TYPES = new Set(["string", "int", "float64", "bool"]);
|
|
2327
|
+
function collectNullishConsumedPropNames(ctx, ir) {
|
|
2328
|
+
const names = new Set;
|
|
2329
|
+
const optionalParams = new Set(ir.metadata.propsParams.filter((p) => p.optional && p.defaultValue == null).map((p) => p.name));
|
|
2330
|
+
if (optionalParams.size === 0)
|
|
2331
|
+
return names;
|
|
2332
|
+
const propsObject = ctx.state.propsObjectName;
|
|
2333
|
+
const propNameOfLeft = (left) => {
|
|
2334
|
+
if (left.kind === "identifier")
|
|
2335
|
+
return left.name;
|
|
2336
|
+
if (left.kind === "member" && !left.computed && left.object.kind === "identifier" && left.object.name === propsObject) {
|
|
2337
|
+
return left.property;
|
|
2338
|
+
}
|
|
2339
|
+
return null;
|
|
2340
|
+
};
|
|
2341
|
+
const isZeroEquivalentLiteral = (right) => right.kind === "literal" && (right.value === "" || right.value === false || right.value === null || right.literalType === "number" && Number(right.value) === 0);
|
|
2342
|
+
const walk = (node) => {
|
|
2343
|
+
if (!node || typeof node !== "object")
|
|
2344
|
+
return;
|
|
2345
|
+
if (Array.isArray(node)) {
|
|
2346
|
+
for (const item of node)
|
|
2347
|
+
walk(item);
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
const rec = node;
|
|
2351
|
+
if (rec.kind === "logical" && rec.op === "??" && rec.left && rec.right) {
|
|
2352
|
+
const propName = propNameOfLeft(rec.left);
|
|
2353
|
+
if (propName && optionalParams.has(propName) && !isZeroEquivalentLiteral(rec.right)) {
|
|
2354
|
+
names.add(propName);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
for (const value of Object.values(rec))
|
|
2358
|
+
walk(value);
|
|
2359
|
+
};
|
|
2360
|
+
walk(ir.root);
|
|
2361
|
+
for (const signal of ir.metadata.signals) {
|
|
2362
|
+
const match = ctx.extractPropFallback(signal.initialValue, signal.parsed);
|
|
2363
|
+
if (!match || !optionalParams.has(match.propName))
|
|
2364
|
+
continue;
|
|
2365
|
+
const f = match.goFallback;
|
|
2366
|
+
if (f === '""' || f === "false" || f === "nil" || Number(f) === 0)
|
|
2367
|
+
continue;
|
|
2368
|
+
names.add(match.propName);
|
|
2369
|
+
}
|
|
2370
|
+
return names;
|
|
2371
|
+
}
|
|
2372
|
+
function collectOmittableAttrConsumedPropNames(ctx, ir) {
|
|
2373
|
+
const names = new Set;
|
|
2374
|
+
const optionalParams = new Set(ir.metadata.propsParams.filter((p) => p.optional && p.defaultValue == null).map((p) => p.name));
|
|
2375
|
+
if (optionalParams.size === 0)
|
|
2376
|
+
return names;
|
|
2377
|
+
const propsObject = ctx.state.propsObjectName;
|
|
2378
|
+
const walk = (node) => {
|
|
2379
|
+
if (!node || typeof node !== "object")
|
|
2380
|
+
return;
|
|
2381
|
+
if (Array.isArray(node)) {
|
|
2382
|
+
for (const item of node)
|
|
2383
|
+
walk(item);
|
|
2384
|
+
return;
|
|
2385
|
+
}
|
|
2386
|
+
const rec = node;
|
|
2387
|
+
if (rec.type === "element" && Array.isArray(rec.attrs)) {
|
|
2388
|
+
for (const attr of rec.attrs) {
|
|
2389
|
+
if (attr.name === "class" || attr.name === "className" || attr.name === "style")
|
|
2390
|
+
continue;
|
|
2391
|
+
if (isBooleanAttr(attr.name))
|
|
2392
|
+
continue;
|
|
2393
|
+
if (attr.value?.kind !== "expression" || attr.value.presenceOrUndefined)
|
|
2394
|
+
continue;
|
|
2395
|
+
const bareId = String(attr.value.expr ?? "").trim();
|
|
2396
|
+
const propName = propsObject && bareId.startsWith(`${propsObject}.`) ? bareId.slice(propsObject.length + 1) : bareId;
|
|
2397
|
+
if (/^[A-Za-z_$][\w$]*$/.test(propName) && optionalParams.has(propName)) {
|
|
2398
|
+
names.add(propName);
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
for (const value of Object.values(rec))
|
|
2403
|
+
walk(value);
|
|
2404
|
+
};
|
|
2405
|
+
walk(ir.root);
|
|
2406
|
+
return names;
|
|
2407
|
+
}
|
|
2144
2408
|
function resolvePropGoType(ctx, param, propTypeOverrides) {
|
|
2145
2409
|
const base = propTypeOverrides.get(param.name) ?? typeInfoToGo(ctx, param.type, param.defaultValue);
|
|
2146
2410
|
if (param.optional && ctx.state.localStructFields.has(base)) {
|
|
2147
2411
|
return "map[string]interface{}";
|
|
2148
2412
|
}
|
|
2413
|
+
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)) {
|
|
2414
|
+
return "interface{}";
|
|
2415
|
+
}
|
|
2149
2416
|
return base;
|
|
2150
2417
|
}
|
|
2151
2418
|
function collectNillablePropNames(ctx, ir) {
|
|
@@ -2160,6 +2427,7 @@ function collectNillablePropNames(ctx, ir) {
|
|
|
2160
2427
|
}
|
|
2161
2428
|
|
|
2162
2429
|
// src/adapter/props/prop-classes.ts
|
|
2430
|
+
import { collectLoopBoundNames } from "@barefootjs/jsx";
|
|
2163
2431
|
function isStringTypeInfo(type) {
|
|
2164
2432
|
return type.kind === "primitive" && type.primitive === "string";
|
|
2165
2433
|
}
|
|
@@ -2180,6 +2448,13 @@ function collectStringValueNames(ir) {
|
|
|
2180
2448
|
if (isStringTypeInfo(p.type))
|
|
2181
2449
|
names.add(p.name);
|
|
2182
2450
|
}
|
|
2451
|
+
for (const c of ir.metadata.localConstants) {
|
|
2452
|
+
if (c.type !== null && isStringTypeInfo(c.type) || isBareStringLiteral(c.value)) {
|
|
2453
|
+
names.add(c.name);
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
for (const bound of collectLoopBoundNames(ir))
|
|
2457
|
+
names.delete(bound);
|
|
2183
2458
|
return names;
|
|
2184
2459
|
}
|
|
2185
2460
|
|
|
@@ -2216,14 +2491,15 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2216
2491
|
state: this.state,
|
|
2217
2492
|
convertExpressionToGo: (jsExpr, out, preParsed) => this.convertExpressionToGo(jsExpr, out, preParsed),
|
|
2218
2493
|
convertConditionToGo: (jsCondition, preParsed) => this.convertConditionToGo(jsCondition, preParsed),
|
|
2219
|
-
extractPropNameFromInitialValue: (initialValue) => this.extractPropNameFromInitialValue(initialValue),
|
|
2220
|
-
extractPropFallback: (initialValue) => this.extractPropFallback(initialValue),
|
|
2494
|
+
extractPropNameFromInitialValue: (initialValue, preParsed) => this.extractPropNameFromInitialValue(initialValue, preParsed),
|
|
2495
|
+
extractPropFallback: (initialValue, preParsed) => this.extractPropFallback(initialValue, preParsed),
|
|
2221
2496
|
resolveModuleStringConst: (name) => this.resolveModuleStringConst(name)
|
|
2222
2497
|
};
|
|
2223
2498
|
get errors() {
|
|
2224
2499
|
return this.state.errors;
|
|
2225
2500
|
}
|
|
2226
2501
|
inLoop = false;
|
|
2502
|
+
bakedStaticChildLoopCache = new Map;
|
|
2227
2503
|
loopParamStack = [];
|
|
2228
2504
|
loopKeyDepthStack = [];
|
|
2229
2505
|
loopScalarItemStack = [];
|
|
@@ -2231,6 +2507,8 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2231
2507
|
loopVarRefCount = new Map;
|
|
2232
2508
|
loopBindingStack = [];
|
|
2233
2509
|
loopRestExcludeStack = [];
|
|
2510
|
+
staticLoopItemStack = [];
|
|
2511
|
+
staticLoopBakeFailed = false;
|
|
2234
2512
|
childComponentShapes = new Map;
|
|
2235
2513
|
childContextConsumers = new Map;
|
|
2236
2514
|
constructor(options = {}) {
|
|
@@ -2246,6 +2524,8 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2246
2524
|
this.state.restPropsName = ir.metadata.restPropsName ?? null;
|
|
2247
2525
|
this.state.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
|
|
2248
2526
|
this.state.localConstants = ir.metadata.localConstants ?? [];
|
|
2527
|
+
this.state.staticLoopSourceBoundNames = collectLoopBoundNames2(ir);
|
|
2528
|
+
this.bakedStaticChildLoopCache = new Map;
|
|
2249
2529
|
this.state.localHelperNames = new Set(this.state.localConstants.filter((c) => !c.isModule && c.containsArrow).map((c) => c.name));
|
|
2250
2530
|
this.state.currentMemos = ir.metadata.memos ?? [];
|
|
2251
2531
|
this.state.currentTypeDefinitions = ir.metadata.typeDefinitions ?? [];
|
|
@@ -2264,6 +2544,10 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2264
2544
|
}
|
|
2265
2545
|
this.state.loweringMatchers = prepareLoweringMatchers(ir.metadata);
|
|
2266
2546
|
augmentInheritedPropAccesses(ir);
|
|
2547
|
+
this.buildLocalTypeTables(ir, ir.metadata.componentName);
|
|
2548
|
+
this.state.nullishConsumedPropNames = collectNullishConsumedPropNames(this.emitCtx, ir);
|
|
2549
|
+
this.state.omittableAttrConsumedPropNames = collectOmittableAttrConsumedPropNames(this.emitCtx, ir);
|
|
2550
|
+
this.state.nillablePropNames = collectNillablePropNames(this.emitCtx, ir);
|
|
2267
2551
|
}
|
|
2268
2552
|
generate(ir, options) {
|
|
2269
2553
|
this.state.componentName = ir.metadata.componentName;
|
|
@@ -2272,7 +2556,6 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
2272
2556
|
this.state.templateVarCounter = 0;
|
|
2273
2557
|
this.state.pendingChildrenDefines = [];
|
|
2274
2558
|
this.primeCompileState(ir);
|
|
2275
|
-
this.state.nillablePropNames = collectNillablePropNames(this.emitCtx, ir);
|
|
2276
2559
|
this.state.stringValueNames = collectStringValueNames(ir);
|
|
2277
2560
|
if (!options?.siblingTemplatesRegistered) {
|
|
2278
2561
|
this.checkImportedLoopChildComponents(ir);
|
|
@@ -2627,6 +2910,8 @@ ${goFields.join(`
|
|
|
2627
2910
|
lines.push(` ${fieldName} ${goType}`);
|
|
2628
2911
|
}
|
|
2629
2912
|
for (const nested of inputNested) {
|
|
2913
|
+
if (nested.loopMarkerId && this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey))
|
|
2914
|
+
continue;
|
|
2630
2915
|
lines.push(` ${nested.name}s []${nested.name}Input`);
|
|
2631
2916
|
}
|
|
2632
2917
|
const takenInput = new Set(ir.metadata.propsParams.map((p) => capitalizeFieldName(p.name)));
|
|
@@ -2758,6 +3043,21 @@ ${goFields.join(`
|
|
|
2758
3043
|
const staticWithoutBody = staticNested.filter((n) => !n.bodyChildren || n.bodyChildren.length === 0);
|
|
2759
3044
|
for (const nested of staticWithoutBody) {
|
|
2760
3045
|
const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
|
|
3046
|
+
const baked = nested.loopMarkerId ? this.getBakedStaticChildLoop(nested.loopMarkerId, nested, nested.loopArrayParsed, nested.loopParam, nested.loopKey) : null;
|
|
3047
|
+
if (baked) {
|
|
3048
|
+
lines.push(` ${varName} := make([]${nested.name}Props, ${baked.items.length})`);
|
|
3049
|
+
baked.items.forEach((item, i) => {
|
|
3050
|
+
const fields = item.inputFields.map((f) => `${f.goField}: ${f.goValue}`).join(", ");
|
|
3051
|
+
lines.push(` ${varName}[${i}] = New${nested.name}Props(${nested.name}Input{${fields}})`);
|
|
3052
|
+
lines.push(` ${varName}[${i}].BfParent = scopeID`);
|
|
3053
|
+
lines.push(` ${varName}[${i}].BfMount = "${nested.slotId}"`);
|
|
3054
|
+
if (item.dataKey !== null) {
|
|
3055
|
+
lines.push(` ${varName}[${i}].BfDataKey = ${JSON.stringify(item.dataKey)}`);
|
|
3056
|
+
}
|
|
3057
|
+
});
|
|
3058
|
+
lines.push("");
|
|
3059
|
+
continue;
|
|
3060
|
+
}
|
|
2761
3061
|
lines.push(` ${varName} := make([]${nested.name}Props, len(in.${nested.name}s))`);
|
|
2762
3062
|
lines.push(` for i, item := range in.${nested.name}s {`);
|
|
2763
3063
|
lines.push(` ${varName}[i] = New${nested.name}Props(item)`);
|
|
@@ -2774,10 +3074,18 @@ ${goFields.join(`
|
|
|
2774
3074
|
this.emitStaticBodyWrappers(lines, ir, componentName, staticWithBody, emittedWrapperVars);
|
|
2775
3075
|
const propFallbackVars = this.collectPropFallbackVars(ir);
|
|
2776
3076
|
for (const [, info] of propFallbackVars) {
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
3077
|
+
if (info.assertType) {
|
|
3078
|
+
const deref = info.assertType === "int" ? `bf.ToInt(in.${info.fieldName})` : info.assertType === "float64" ? `bf.ToFloat64(in.${info.fieldName})` : `in.${info.fieldName}.(${info.assertType})`;
|
|
3079
|
+
lines.push(` var ${info.varName} ${info.assertType} = ${info.goFallback}`);
|
|
3080
|
+
lines.push(` if in.${info.fieldName} != nil {`);
|
|
3081
|
+
lines.push(` ${info.varName} = ${deref}`);
|
|
3082
|
+
lines.push(` }`);
|
|
3083
|
+
} else {
|
|
3084
|
+
lines.push(` ${info.varName} := in.${info.fieldName}`);
|
|
3085
|
+
lines.push(` if ${info.varName} == ${info.zeroLiteral} {`);
|
|
3086
|
+
lines.push(` ${info.varName} = ${info.goFallback}`);
|
|
3087
|
+
lines.push(` }`);
|
|
3088
|
+
}
|
|
2781
3089
|
}
|
|
2782
3090
|
if (propFallbackVars.size > 0)
|
|
2783
3091
|
lines.push("");
|
|
@@ -2857,7 +3165,7 @@ ${goFields.join(`
|
|
|
2857
3165
|
const fieldName = capitalizeFieldName(signal.getter);
|
|
2858
3166
|
if (propFieldNames.has(fieldName))
|
|
2859
3167
|
continue;
|
|
2860
|
-
const fallbackMatch = this.extractPropFallback(signal.initialValue);
|
|
3168
|
+
const fallbackMatch = this.extractPropFallback(signal.initialValue, signal.parsed);
|
|
2861
3169
|
const hoisted = fallbackMatch ? propFallbackVars.get(fallbackMatch.propName) : undefined;
|
|
2862
3170
|
if (hoisted) {
|
|
2863
3171
|
lines.push(` ${fieldName}: ${hoisted.varName},`);
|
|
@@ -3703,8 +4011,9 @@ ${goFields.join(`
|
|
|
3703
4011
|
for (const nested of findNestedComponents(ir.root)) {
|
|
3704
4012
|
localTaken.add(`${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`);
|
|
3705
4013
|
}
|
|
4014
|
+
const propTypeOverrides = buildPropTypeOverrides(this.emitCtx, ir);
|
|
3706
4015
|
for (const signal of ir.metadata.signals) {
|
|
3707
|
-
const match = this.extractPropFallback(signal.initialValue);
|
|
4016
|
+
const match = this.extractPropFallback(signal.initialValue, signal.parsed);
|
|
3708
4017
|
if (!match)
|
|
3709
4018
|
continue;
|
|
3710
4019
|
if (result.has(match.propName))
|
|
@@ -3715,6 +4024,8 @@ ${goFields.join(`
|
|
|
3715
4024
|
if (goPropDefault(param.defaultValue) !== null)
|
|
3716
4025
|
continue;
|
|
3717
4026
|
const fieldName = capitalizeFieldName(match.propName);
|
|
4027
|
+
const concreteType = propTypeOverrides.get(param.name) ?? typeInfoToGo(this.emitCtx, param.type, param.defaultValue);
|
|
4028
|
+
const nullishLowered = NULLISH_SCALAR_GO_TYPES.has(concreteType) && resolvePropGoType(this.emitCtx, param, propTypeOverrides) === "interface{}";
|
|
3718
4029
|
let zeroLiteral;
|
|
3719
4030
|
if (match.goFallback === "true" || match.goFallback === "false") {
|
|
3720
4031
|
zeroLiteral = "false";
|
|
@@ -3725,20 +4036,31 @@ ${goFields.join(`
|
|
|
3725
4036
|
} else {
|
|
3726
4037
|
continue;
|
|
3727
4038
|
}
|
|
3728
|
-
if (
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
4039
|
+
if (!nullishLowered) {
|
|
4040
|
+
if (match.goFallback === zeroLiteral)
|
|
4041
|
+
continue;
|
|
4042
|
+
if (zeroLiteral === "0" && Number(match.goFallback) === 0)
|
|
4043
|
+
continue;
|
|
4044
|
+
}
|
|
3732
4045
|
let varName = match.propName;
|
|
3733
4046
|
while (localTaken.has(varName) || GO_KEYWORDS.has(varName)) {
|
|
3734
4047
|
varName += "_";
|
|
3735
4048
|
}
|
|
3736
4049
|
localTaken.add(varName);
|
|
3737
|
-
result.set(match.propName, {
|
|
4050
|
+
result.set(match.propName, {
|
|
4051
|
+
varName,
|
|
4052
|
+
fieldName,
|
|
4053
|
+
goFallback: match.goFallback,
|
|
4054
|
+
zeroLiteral,
|
|
4055
|
+
...nullishLowered ? { assertType: concreteType } : {}
|
|
4056
|
+
});
|
|
3738
4057
|
}
|
|
3739
4058
|
return result;
|
|
3740
4059
|
}
|
|
3741
|
-
extractPropFallback(initialValue) {
|
|
4060
|
+
extractPropFallback(initialValue, preParsed) {
|
|
4061
|
+
const structural = preParsed ? this.extractPropFallbackFromParsed(preParsed) : null;
|
|
4062
|
+
if (structural)
|
|
4063
|
+
return structural;
|
|
3742
4064
|
if (!this.state.propsObjectName)
|
|
3743
4065
|
return null;
|
|
3744
4066
|
const trimmed = initialValue.trim();
|
|
@@ -3752,9 +4074,38 @@ ${goFields.join(`
|
|
|
3752
4074
|
return null;
|
|
3753
4075
|
return { propName: m[1], goFallback };
|
|
3754
4076
|
}
|
|
3755
|
-
|
|
3756
|
-
if (
|
|
4077
|
+
extractPropFallbackFromParsed(preParsed) {
|
|
4078
|
+
if (preParsed.kind !== "logical" || preParsed.op !== "??")
|
|
3757
4079
|
return null;
|
|
4080
|
+
const left = preParsed.left;
|
|
4081
|
+
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;
|
|
4082
|
+
if (!propName)
|
|
4083
|
+
return null;
|
|
4084
|
+
let right = preParsed.right;
|
|
4085
|
+
let negate = "";
|
|
4086
|
+
if (right.kind === "unary" && right.op === "-") {
|
|
4087
|
+
negate = "-";
|
|
4088
|
+
right = right.argument;
|
|
4089
|
+
}
|
|
4090
|
+
if (right.kind !== "literal")
|
|
4091
|
+
return null;
|
|
4092
|
+
if (negate && right.literalType !== "number")
|
|
4093
|
+
return null;
|
|
4094
|
+
if (right.literalType === "string") {
|
|
4095
|
+
return { propName, goFallback: JSON.stringify(right.value) };
|
|
4096
|
+
}
|
|
4097
|
+
const goFallback = goPropDefault(negate + (right.raw ?? String(right.value)));
|
|
4098
|
+
if (goFallback === null)
|
|
4099
|
+
return null;
|
|
4100
|
+
return { propName, goFallback };
|
|
4101
|
+
}
|
|
4102
|
+
extractPropNameFromInitialValue(initialValue, preParsed) {
|
|
4103
|
+
if (!this.state.propsObjectName) {
|
|
4104
|
+
if (preParsed?.kind === "logical" && (preParsed.op === "??" || preParsed.op === "||") && preParsed.left.kind === "identifier") {
|
|
4105
|
+
return preParsed.left.name;
|
|
4106
|
+
}
|
|
4107
|
+
return null;
|
|
4108
|
+
}
|
|
3758
4109
|
const trimmed = initialValue.trim();
|
|
3759
4110
|
const name = this.state.propsObjectName;
|
|
3760
4111
|
const direct = new RegExp(`^${name}\\.(\\w+)(?:\\s*(?:\\?\\?|\\|\\|)\\s*.+)?$`);
|
|
@@ -3811,7 +4162,8 @@ ${goFields.join(`
|
|
|
3811
4162
|
renderElement(element) {
|
|
3812
4163
|
const tag = element.tag;
|
|
3813
4164
|
const attrs = this.renderAttributes(element);
|
|
3814
|
-
const
|
|
4165
|
+
const dangerousHtml = this.renderDangerousInnerHtml(element);
|
|
4166
|
+
const children = dangerousHtml !== null ? dangerousHtml : this.renderChildren(element.children);
|
|
3815
4167
|
let hydrationAttrs = "";
|
|
3816
4168
|
if (element.needsScope) {
|
|
3817
4169
|
hydrationAttrs += ` ${this.renderScopeMarker(".ScopeID")}`;
|
|
@@ -3846,6 +4198,22 @@ ${goFields.join(`
|
|
|
3846
4198
|
}
|
|
3847
4199
|
return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
|
|
3848
4200
|
}
|
|
4201
|
+
renderDangerousInnerHtml(element) {
|
|
4202
|
+
const resolution = resolveDangerousInnerHtml(element);
|
|
4203
|
+
if (!resolution)
|
|
4204
|
+
return null;
|
|
4205
|
+
if (resolution.kind === "dynamic") {
|
|
4206
|
+
this.errors.push(dangerousInnerHtmlDiagnostic(resolution.expr, resolution.loc));
|
|
4207
|
+
return "";
|
|
4208
|
+
}
|
|
4209
|
+
const violation = dangerousInnerHtmlMetacharViolation(resolution.html, this.name);
|
|
4210
|
+
if (violation) {
|
|
4211
|
+
const attr = element.attrs.find(isDangerousInnerHtmlAttr);
|
|
4212
|
+
this.errors.push(dangerousInnerHtmlDiagnostic(`{ __html: ${JSON.stringify(resolution.html)} }`, attr.loc, violation));
|
|
4213
|
+
return "";
|
|
4214
|
+
}
|
|
4215
|
+
return resolution.html;
|
|
4216
|
+
}
|
|
3849
4217
|
renderExpression(expr) {
|
|
3850
4218
|
if (expr.clientOnly) {
|
|
3851
4219
|
if (expr.slotId) {
|
|
@@ -4149,8 +4517,20 @@ ${goFields.join(`
|
|
|
4149
4517
|
const wrapRight = wrapIfMultiToken(emit(right));
|
|
4150
4518
|
if (op === "&&")
|
|
4151
4519
|
return `and ${wrapLeft} ${wrapRight}`;
|
|
4520
|
+
if (op === "??" && this.nillablePropNameOf(left) !== null) {
|
|
4521
|
+
return `bf_nullish ${wrapLeft} ${wrapRight}`;
|
|
4522
|
+
}
|
|
4152
4523
|
return `or ${wrapLeft} ${wrapRight}`;
|
|
4153
4524
|
}
|
|
4525
|
+
nillablePropNameOf(expr) {
|
|
4526
|
+
let name = null;
|
|
4527
|
+
if (expr.kind === "identifier") {
|
|
4528
|
+
name = expr.name;
|
|
4529
|
+
} else if (expr.kind === "member" && !expr.computed && expr.object.kind === "identifier" && expr.object.name === this.state.propsObjectName) {
|
|
4530
|
+
name = expr.property;
|
|
4531
|
+
}
|
|
4532
|
+
return name !== null && this.state.nullishConsumedPropNames.has(name) && this.state.nillablePropNames.has(name) ? name : null;
|
|
4533
|
+
}
|
|
4154
4534
|
conditional(test, consequent, alternate, emit) {
|
|
4155
4535
|
const t = emit(test);
|
|
4156
4536
|
const c = this.renderConditionalBranch(consequent);
|
|
@@ -4579,8 +4959,8 @@ ${goFields.join(`
|
|
|
4579
4959
|
const value = negated ? "false" : "true";
|
|
4580
4960
|
return `len (bf_filter ${arrayExpr} "${field}" ${value})`;
|
|
4581
4961
|
}
|
|
4582
|
-
renderPredicateCondition(pred, param) {
|
|
4583
|
-
return this.renderFilterExpr(pred, param);
|
|
4962
|
+
renderPredicateCondition(pred, param, datumField) {
|
|
4963
|
+
return this.renderFilterExpr(pred, param, new Map, datumField ?? undefined);
|
|
4584
4964
|
}
|
|
4585
4965
|
needsParens(expr) {
|
|
4586
4966
|
return expr.kind === "logical" || expr.kind === "unary" || expr.kind === "conditional";
|
|
@@ -4601,21 +4981,23 @@ ${goFields.join(`
|
|
|
4601
4981
|
}
|
|
4602
4982
|
return null;
|
|
4603
4983
|
}
|
|
4604
|
-
renderFilterExpr(expr, param, localVarMap = new Map) {
|
|
4984
|
+
renderFilterExpr(expr, param, localVarMap = new Map, datumField) {
|
|
4605
4985
|
if (this.filterExprDepth === 0)
|
|
4606
4986
|
this.filterExprUnsupported = false;
|
|
4607
4987
|
this.filterExprDepth++;
|
|
4608
4988
|
try {
|
|
4609
|
-
return this.renderFilterExprNode(expr, param, localVarMap);
|
|
4989
|
+
return this.renderFilterExprNode(expr, param, localVarMap, datumField);
|
|
4610
4990
|
} finally {
|
|
4611
4991
|
this.filterExprDepth--;
|
|
4612
4992
|
}
|
|
4613
4993
|
}
|
|
4614
|
-
renderFilterExprNode(expr, param, localVarMap) {
|
|
4994
|
+
renderFilterExprNode(expr, param, localVarMap, datumField) {
|
|
4995
|
+
const paramPrefix = datumField ? `.${datumField}` : "";
|
|
4996
|
+
const paramDot = paramPrefix || ".";
|
|
4615
4997
|
switch (expr.kind) {
|
|
4616
4998
|
case "identifier": {
|
|
4617
4999
|
if (expr.name === param) {
|
|
4618
|
-
return
|
|
5000
|
+
return paramDot;
|
|
4619
5001
|
}
|
|
4620
5002
|
const signal = localVarMap.get(expr.name);
|
|
4621
5003
|
if (signal) {
|
|
@@ -4633,24 +5015,24 @@ ${goFields.join(`
|
|
|
4633
5015
|
return String(expr.value);
|
|
4634
5016
|
case "member": {
|
|
4635
5017
|
if (expr.object.kind === "identifier" && expr.object.name === param) {
|
|
4636
|
-
return
|
|
5018
|
+
return `${paramPrefix}.${capitalizeFieldName(expr.property)}`;
|
|
4637
5019
|
}
|
|
4638
5020
|
if (expr.property === "length") {
|
|
4639
5021
|
const innerHO = this.higherOrderShapeOf(expr.object);
|
|
4640
5022
|
if (innerHO && innerHO.method === "filter") {
|
|
4641
|
-
const lenExpr = this.renderFilterLengthExpr(innerHO, (e) => this.renderFilterExpr(e, param, localVarMap));
|
|
5023
|
+
const lenExpr = this.renderFilterLengthExpr(innerHO, (e) => this.renderFilterExpr(e, param, localVarMap, datumField));
|
|
4642
5024
|
if (lenExpr)
|
|
4643
5025
|
return `(${lenExpr})`;
|
|
4644
5026
|
}
|
|
4645
5027
|
}
|
|
4646
|
-
const obj = this.renderFilterExpr(expr.object, param, localVarMap);
|
|
5028
|
+
const obj = this.renderFilterExpr(expr.object, param, localVarMap, datumField);
|
|
4647
5029
|
if (this.filterExprUnsupported)
|
|
4648
5030
|
return "false";
|
|
4649
5031
|
return `${obj}.${capitalizeFieldName(expr.property)}`;
|
|
4650
5032
|
}
|
|
4651
5033
|
case "call": {
|
|
4652
5034
|
if (expr.callee.kind === "member" && expr.callee.object.kind === "identifier" && expr.callee.object.name === param) {
|
|
4653
|
-
return
|
|
5035
|
+
return `${paramPrefix}.${capitalizeFieldName(expr.callee.property)}`;
|
|
4654
5036
|
}
|
|
4655
5037
|
if (expr.callee.kind === "identifier" && expr.args.length === 0) {
|
|
4656
5038
|
return `$.${capitalizeFieldName(expr.callee.name)}`;
|
|
@@ -4658,13 +5040,13 @@ ${goFields.join(`
|
|
|
4658
5040
|
if (asCallbackMethodCall3(expr) !== null) {
|
|
4659
5041
|
return this.refuseFilterExprNode(expr);
|
|
4660
5042
|
}
|
|
4661
|
-
const result = this.renderFilterExpr(expr.callee, param, localVarMap);
|
|
5043
|
+
const result = this.renderFilterExpr(expr.callee, param, localVarMap, datumField);
|
|
4662
5044
|
if (this.filterExprUnsupported)
|
|
4663
5045
|
return "false";
|
|
4664
5046
|
return result;
|
|
4665
5047
|
}
|
|
4666
5048
|
case "unary": {
|
|
4667
|
-
const arg = this.renderFilterExpr(expr.argument, param, localVarMap);
|
|
5049
|
+
const arg = this.renderFilterExpr(expr.argument, param, localVarMap, datumField);
|
|
4668
5050
|
if (this.filterExprUnsupported)
|
|
4669
5051
|
return "false";
|
|
4670
5052
|
if (expr.op === "!") {
|
|
@@ -4677,10 +5059,10 @@ ${goFields.join(`
|
|
|
4677
5059
|
return arg;
|
|
4678
5060
|
}
|
|
4679
5061
|
case "binary": {
|
|
4680
|
-
const left = this.renderFilterExpr(expr.left, param, localVarMap);
|
|
5062
|
+
const left = this.renderFilterExpr(expr.left, param, localVarMap, datumField);
|
|
4681
5063
|
if (this.filterExprUnsupported)
|
|
4682
5064
|
return "false";
|
|
4683
|
-
const right = this.renderFilterExpr(expr.right, param, localVarMap);
|
|
5065
|
+
const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField);
|
|
4684
5066
|
if (this.filterExprUnsupported)
|
|
4685
5067
|
return "false";
|
|
4686
5068
|
switch (expr.op) {
|
|
@@ -4711,10 +5093,10 @@ ${goFields.join(`
|
|
|
4711
5093
|
}
|
|
4712
5094
|
}
|
|
4713
5095
|
case "logical": {
|
|
4714
|
-
const left = this.renderFilterExpr(expr.left, param, localVarMap);
|
|
5096
|
+
const left = this.renderFilterExpr(expr.left, param, localVarMap, datumField);
|
|
4715
5097
|
if (this.filterExprUnsupported)
|
|
4716
5098
|
return "false";
|
|
4717
|
-
const right = this.renderFilterExpr(expr.right, param, localVarMap);
|
|
5099
|
+
const right = this.renderFilterExpr(expr.right, param, localVarMap, datumField);
|
|
4718
5100
|
if (this.filterExprUnsupported)
|
|
4719
5101
|
return "false";
|
|
4720
5102
|
if (expr.op === "&&") {
|
|
@@ -4782,11 +5164,22 @@ ${goFields.join(`
|
|
|
4782
5164
|
if (trimmed === "null" || trimmed === "undefined") {
|
|
4783
5165
|
return '""';
|
|
4784
5166
|
}
|
|
5167
|
+
if (this.staticLoopItemStack.length > 0) {
|
|
5168
|
+
const top = this.staticLoopItemStack[this.staticLoopItemStack.length - 1];
|
|
5169
|
+
const parsedForBake = preParsed ?? parseExpression4(trimmed);
|
|
5170
|
+
const resolved = evaluateStaticLiteral3(parsedForBake, new Map([[top.param, top.item]]));
|
|
5171
|
+
const literal = resolved !== null ? scalarToGoLiteral(resolved.value) : null;
|
|
5172
|
+
if (literal !== null) {
|
|
5173
|
+
return literal;
|
|
5174
|
+
}
|
|
5175
|
+
this.staticLoopBakeFailed = true;
|
|
5176
|
+
return '""';
|
|
5177
|
+
}
|
|
4785
5178
|
const staticIndexed = this.resolveStaticRecordLiteralIndex(trimmed);
|
|
4786
5179
|
if (staticIndexed !== null) {
|
|
4787
5180
|
return staticIndexed;
|
|
4788
5181
|
}
|
|
4789
|
-
if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
|
|
5182
|
+
if (!this.isLoopShadowedName(trimmed) && /^[A-Za-z_$][\w$]*$/.test(trimmed)) {
|
|
4790
5183
|
const litConst = (this.state.localConstants ?? []).find((c) => c.name === trimmed);
|
|
4791
5184
|
if (litConst?.value !== undefined) {
|
|
4792
5185
|
const v = litConst.value.trim();
|
|
@@ -4804,7 +5197,7 @@ ${goFields.join(`
|
|
|
4804
5197
|
if (inlined !== null) {
|
|
4805
5198
|
return this.convertExpressionToGo(stringifyParsedExpr2(inlined), out, inlined);
|
|
4806
5199
|
}
|
|
4807
|
-
const parsed = preParsed ??
|
|
5200
|
+
const parsed = preParsed ?? parseExpression4(trimmed);
|
|
4808
5201
|
const support = isSupported(parsed);
|
|
4809
5202
|
if (!support.supported) {
|
|
4810
5203
|
this.state.errors.push({
|
|
@@ -4822,10 +5215,15 @@ ${goFields.join(`
|
|
|
4822
5215
|
out.parsed = parsed;
|
|
4823
5216
|
return this.renderParsedExpr(parsed);
|
|
4824
5217
|
}
|
|
5218
|
+
isLoopShadowedName(name) {
|
|
5219
|
+
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));
|
|
5220
|
+
}
|
|
4825
5221
|
resolveStaticRecordLiteralIndex(jsExpr) {
|
|
4826
5222
|
const m = /^([A-Za-z_$][\w$]*)\[\s*(?:'([^']*)'|"([^"]*)")\s*\]$/.exec(jsExpr) ?? /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/.exec(jsExpr);
|
|
4827
5223
|
if (!m)
|
|
4828
5224
|
return null;
|
|
5225
|
+
if (this.isLoopShadowedName(m[1]))
|
|
5226
|
+
return null;
|
|
4829
5227
|
const key = m[2] ?? m[3];
|
|
4830
5228
|
const constInfo = (this.state.localConstants ?? []).find((c) => c.name === m[1] && c.isModule);
|
|
4831
5229
|
if (constInfo?.value === undefined)
|
|
@@ -4931,7 +5329,7 @@ ${goFields.join(`
|
|
|
4931
5329
|
}
|
|
4932
5330
|
convertConditionToGo(jsCondition, preParsed) {
|
|
4933
5331
|
const trimmed = jsCondition.trim();
|
|
4934
|
-
const parsed = preParsed ??
|
|
5332
|
+
const parsed = preParsed ?? parseExpression4(trimmed);
|
|
4935
5333
|
const support = isSupported(parsed);
|
|
4936
5334
|
if (!support.supported) {
|
|
4937
5335
|
this.state.errors.push({
|
|
@@ -5152,6 +5550,29 @@ ${goFields.join(`
|
|
|
5152
5550
|
}
|
|
5153
5551
|
return;
|
|
5154
5552
|
}
|
|
5553
|
+
wrapperDatumField(loop) {
|
|
5554
|
+
if (!loop.childComponent)
|
|
5555
|
+
return null;
|
|
5556
|
+
for (const prop of loop.childComponent.props) {
|
|
5557
|
+
if (prop.isEventHandler)
|
|
5558
|
+
continue;
|
|
5559
|
+
if (prop.value.kind !== "expression")
|
|
5560
|
+
continue;
|
|
5561
|
+
const parsed = prop.value.parsed;
|
|
5562
|
+
const isBareParamRef = parsed ? parsed.kind === "identifier" && parsed.name === loop.param : prop.value.expr.trim() === loop.param;
|
|
5563
|
+
if (isBareParamRef)
|
|
5564
|
+
return capitalizeFieldName(prop.name);
|
|
5565
|
+
}
|
|
5566
|
+
return null;
|
|
5567
|
+
}
|
|
5568
|
+
getBakedStaticChildLoop(markerId, childComponent, arrayParsed, param, key) {
|
|
5569
|
+
if (this.bakedStaticChildLoopCache.has(markerId)) {
|
|
5570
|
+
return this.bakedStaticChildLoopCache.get(markerId) ?? null;
|
|
5571
|
+
}
|
|
5572
|
+
const result = analyzeBakeableStaticChildLoop({ props: childComponent.props, loopArrayParsed: arrayParsed, loopParam: param, loopKey: key }, this.state.localConstants, { isNameShadowed: (name) => this.state.staticLoopSourceBoundNames.has(name) });
|
|
5573
|
+
this.bakedStaticChildLoopCache.set(markerId, result);
|
|
5574
|
+
return result;
|
|
5575
|
+
}
|
|
5155
5576
|
renderLoop(loop) {
|
|
5156
5577
|
if (loop.clientOnly) {
|
|
5157
5578
|
return `{{bfComment "loop:${loop.markerId}"}}{{bfComment "/loop:${loop.markerId}"}}`;
|
|
@@ -5172,8 +5593,13 @@ ${goFields.join(`
|
|
|
5172
5593
|
}
|
|
5173
5594
|
});
|
|
5174
5595
|
}
|
|
5596
|
+
const bakedChildLoop = loop.childComponent ? this.getBakedStaticChildLoop(loop.markerId, loop.childComponent, loop.arrayParsed, loop.param, loop.key ?? undefined) : null;
|
|
5597
|
+
const bakedElementLoop = loop.childComponent ? null : analyzeBakeableStaticElementLoop(loop, this.state.localConstants, { isNameShadowed: (name) => this.state.staticLoopSourceBoundNames.has(name) });
|
|
5598
|
+
if (bakedElementLoop) {
|
|
5599
|
+
return this.renderUnrolledStaticElementLoop(loop, bakedElementLoop.items);
|
|
5600
|
+
}
|
|
5175
5601
|
const arrayName = loop.array.trim();
|
|
5176
|
-
if (/^[A-Za-z_$][\w$]*$/.test(arrayName)) {
|
|
5602
|
+
if (bakedChildLoop === null && /^[A-Za-z_$][\w$]*$/.test(arrayName)) {
|
|
5177
5603
|
const arrayConst = this.state.localConstants.find((c) => c.name === arrayName);
|
|
5178
5604
|
if (arrayConst && !arrayConst.isModule && arrayConst.parsed && !this.isStringExpr(arrayConst.parsed, new Set)) {
|
|
5179
5605
|
this.state.errors.push({
|
|
@@ -5187,7 +5613,7 @@ ${goFields.join(`
|
|
|
5187
5613
|
});
|
|
5188
5614
|
}
|
|
5189
5615
|
}
|
|
5190
|
-
let goArray = this.convertExpressionToGo(loop.array);
|
|
5616
|
+
let goArray = loop.childComponent ? "" : this.convertExpressionToGo(loop.array);
|
|
5191
5617
|
const param = loop.param;
|
|
5192
5618
|
let index = loop.index || "_";
|
|
5193
5619
|
let rangeIndex = index;
|
|
@@ -5255,7 +5681,8 @@ ${goFields.join(`
|
|
|
5255
5681
|
if (loop.filterPredicate) {
|
|
5256
5682
|
let filterCond;
|
|
5257
5683
|
if (loop.filterPredicate.predicate) {
|
|
5258
|
-
|
|
5684
|
+
const datumField = this.wrapperDatumField(loop);
|
|
5685
|
+
filterCond = this.renderPredicateCondition(loop.filterPredicate.predicate, loop.filterPredicate.param, datumField);
|
|
5259
5686
|
} else {
|
|
5260
5687
|
filterCond = "true";
|
|
5261
5688
|
}
|
|
@@ -5263,6 +5690,38 @@ ${goFields.join(`
|
|
|
5263
5690
|
}
|
|
5264
5691
|
return `{{bfComment "loop:${loop.markerId}"}}{{range $${rangeIndex}, $${rangeValue} := ${goArray}}}${itemMarker}${children}{{end}}{{bfComment "/loop:${loop.markerId}"}}`;
|
|
5265
5692
|
}
|
|
5693
|
+
renderUnrolledStaticElementLoop(loop, items) {
|
|
5694
|
+
this.inLoop = true;
|
|
5695
|
+
this.loopWrapperStack.push(false);
|
|
5696
|
+
this.loopKeyDepthStack.push(loop.depth);
|
|
5697
|
+
this.loopScalarItemStack.push(this.scalarLiteralLoopGoType(loop.arrayParsed, loop.itemType) !== null);
|
|
5698
|
+
this.loopParamStack.push(loop.param);
|
|
5699
|
+
let body = "";
|
|
5700
|
+
for (const item of items) {
|
|
5701
|
+
this.staticLoopItemStack.push({ param: loop.param, item });
|
|
5702
|
+
body += this.renderChildren(loop.children);
|
|
5703
|
+
this.staticLoopItemStack.pop();
|
|
5704
|
+
if (this.staticLoopBakeFailed) {
|
|
5705
|
+
this.staticLoopBakeFailed = false;
|
|
5706
|
+
this.state.errors.push({
|
|
5707
|
+
code: "BF101",
|
|
5708
|
+
severity: "error",
|
|
5709
|
+
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.`,
|
|
5710
|
+
loc: loop.loc ?? this.makeLoc(),
|
|
5711
|
+
suggestion: {
|
|
5712
|
+
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."
|
|
5713
|
+
}
|
|
5714
|
+
});
|
|
5715
|
+
break;
|
|
5716
|
+
}
|
|
5717
|
+
}
|
|
5718
|
+
this.loopParamStack.pop();
|
|
5719
|
+
this.loopScalarItemStack.pop();
|
|
5720
|
+
this.loopKeyDepthStack.pop();
|
|
5721
|
+
this.loopWrapperStack.pop();
|
|
5722
|
+
this.inLoop = false;
|
|
5723
|
+
return `{{bfComment "loop:${loop.markerId}"}}${body}{{bfComment "/loop:${loop.markerId}"}}`;
|
|
5724
|
+
}
|
|
5266
5725
|
loopItemMarker(loop) {
|
|
5267
5726
|
if (loop.bodyIsMultiRoot)
|
|
5268
5727
|
return `{{bfComment "bf-loop-i"}}`;
|
|
@@ -5377,12 +5836,12 @@ ${children}`;
|
|
|
5377
5836
|
if (css !== null)
|
|
5378
5837
|
return `style="${css}"`;
|
|
5379
5838
|
}
|
|
5380
|
-
if (
|
|
5839
|
+
if (isBooleanAttr2(name) || value.presenceOrUndefined) {
|
|
5381
5840
|
const { condition: goCond, preamble } = this.convertConditionToGo(value.expr, value.parsed);
|
|
5382
5841
|
const body = name.startsWith("aria-") ? `${name}="true"` : name;
|
|
5383
5842
|
return `${preamble}{{if ${goCond}}}${body}{{end}}`;
|
|
5384
5843
|
}
|
|
5385
|
-
const parsed = value.parsed ??
|
|
5844
|
+
const parsed = value.parsed ?? parseExpression4(value.expr.trim());
|
|
5386
5845
|
if (parsed.kind === "conditional") {
|
|
5387
5846
|
const undef = (e) => e.kind === "identifier" && (e.name === "undefined" || e.name === "null") || e.kind === "literal" && (e.value === null || e.value === undefined);
|
|
5388
5847
|
const test = parsed.test;
|
|
@@ -5447,7 +5906,7 @@ ${children}`;
|
|
|
5447
5906
|
if (!entries)
|
|
5448
5907
|
return null;
|
|
5449
5908
|
for (const e of entries) {
|
|
5450
|
-
if (e.kind === "expr" && !isSupported(
|
|
5909
|
+
if (e.kind === "expr" && !isSupported(parseExpression4(e.expr)).supported)
|
|
5451
5910
|
return null;
|
|
5452
5911
|
}
|
|
5453
5912
|
return entries.map((e) => e.kind === "literal" ? `${this.escapeAttrText(e.cssKey)}:${this.escapeAttrText(e.value)}` : `${this.escapeAttrText(e.cssKey)}:{{${this.convertExpressionToGo(e.expr)}}}`).join(";");
|
|
@@ -5457,6 +5916,8 @@ ${children}`;
|
|
|
5457
5916
|
for (const attr of element.attrs) {
|
|
5458
5917
|
if (attr.clientOnly)
|
|
5459
5918
|
continue;
|
|
5919
|
+
if (isDangerousInnerHtmlAttr(attr))
|
|
5920
|
+
continue;
|
|
5460
5921
|
let attrName;
|
|
5461
5922
|
if (attr.name === "className")
|
|
5462
5923
|
attrName = "class";
|
|
@@ -5563,22 +6024,15 @@ ${children}`;
|
|
|
5563
6024
|
var goTemplateAdapter = new GoTemplateAdapter;
|
|
5564
6025
|
// src/conformance-pins.ts
|
|
5565
6026
|
var conformancePins = {
|
|
5566
|
-
"static-array-children": [{ code: "BF103", severity: "error" }],
|
|
5567
|
-
"todo-app": [{ code: "BF103", severity: "error" }],
|
|
5568
|
-
"todo-app-ssr": [{ code: "BF103", severity: "error" }],
|
|
5569
6027
|
"static-array-from-props": [{ code: "BF101", severity: "error" }],
|
|
5570
|
-
"static-array-from-props-with-component": [
|
|
5571
|
-
{ code: "BF103", severity: "error" },
|
|
5572
|
-
{ code: "BF101", severity: "error" }
|
|
5573
|
-
],
|
|
6028
|
+
"static-array-from-props-with-component": [{ code: "BF101", severity: "error" }],
|
|
5574
6029
|
"filter-nested-callback-predicate": [
|
|
5575
6030
|
{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2038" }
|
|
5576
6031
|
],
|
|
5577
6032
|
"filter-nested-find-predicate": [
|
|
5578
6033
|
{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2038" }
|
|
5579
6034
|
],
|
|
5580
|
-
"
|
|
5581
|
-
"dangerous-inner-html": [{ code: "BF101", severity: "error" }]
|
|
6035
|
+
"dangerous-inner-html-dynamic": [{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2215" }]
|
|
5582
6036
|
};
|
|
5583
6037
|
// src/render-divergences.ts
|
|
5584
6038
|
var renderDivergences = {};
|