@barefootjs/go-template 0.14.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/go-template-adapter.d.ts +295 -9
- package/dist/adapter/go-template-adapter.d.ts.map +1 -1
- package/dist/adapter/index.js +1282 -137
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +1283 -136
- package/dist/index.js +1282 -137
- package/dist/test-render.d.ts +8 -0
- package/dist/test-render.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/__tests__/build.test.ts +40 -1
- package/src/__tests__/derived-state-memo.test.ts +371 -0
- package/src/__tests__/go-template-adapter.test.ts +267 -7
- package/src/adapter/go-template-adapter.ts +2205 -188
- package/src/build.ts +4 -0
- package/src/test-render.ts +109 -27
package/dist/adapter/index.js
CHANGED
|
@@ -37,9 +37,12 @@ import {
|
|
|
37
37
|
augmentInheritedPropAccesses,
|
|
38
38
|
parseRecordIndexAccess,
|
|
39
39
|
collectContextConsumers,
|
|
40
|
-
isLowerableObjectRestDestructure
|
|
40
|
+
isLowerableObjectRestDestructure,
|
|
41
|
+
collectModuleStringConsts as collectModuleStringConstsShared,
|
|
42
|
+
searchParamsLocalNames
|
|
41
43
|
} from "@barefootjs/jsx";
|
|
42
44
|
import { findInterpolationEnd } from "@barefootjs/jsx/scanner";
|
|
45
|
+
import { BF_REGION } from "@barefootjs/shared";
|
|
43
46
|
function wrapIfMultiToken(rendered) {
|
|
44
47
|
if (rendered.startsWith("(") && rendered.endsWith(")"))
|
|
45
48
|
return rendered;
|
|
@@ -127,13 +130,29 @@ function buildUnsupportedSuggestion(support) {
|
|
|
127
130
|
|
|
128
131
|
${GO_REMEDIATION_OPTIONS}`;
|
|
129
132
|
}
|
|
133
|
+
function wrapGoArg(arg) {
|
|
134
|
+
if (!/\s/.test(arg))
|
|
135
|
+
return arg;
|
|
136
|
+
if (arg.startsWith("(") && arg.endsWith(")"))
|
|
137
|
+
return arg;
|
|
138
|
+
return `(${arg})`;
|
|
139
|
+
}
|
|
140
|
+
function stringTolerantEqOperands(l, r) {
|
|
141
|
+
const isStrLit = (x) => /^"(?:[^"\\]|\\.)*"$/.test(x);
|
|
142
|
+
if (isStrLit(l) === isStrLit(r))
|
|
143
|
+
return [l, r];
|
|
144
|
+
const wrap = (x) => isStrLit(x) ? x : `(bf_string ${wrapGoArg(x)})`;
|
|
145
|
+
return [wrap(l), wrap(r)];
|
|
146
|
+
}
|
|
130
147
|
var GO_TEMPLATE_PRIMITIVES = {
|
|
131
|
-
"JSON.stringify": { arity: 1, emit: (args) => `bf_json ${args[0]}` },
|
|
132
|
-
String: { arity: 1, emit: (args) => `bf_string ${args[0]}` },
|
|
133
|
-
Number: { arity: 1, emit: (args) => `bf_number ${args[0]}` },
|
|
134
|
-
"Math.floor": { arity: 1, emit: (args) => `bf_floor ${args[0]}` },
|
|
135
|
-
"Math.ceil": { arity: 1, emit: (args) => `bf_ceil ${args[0]}` },
|
|
136
|
-
"Math.round": { arity: 1, emit: (args) => `bf_round ${args[0]}` }
|
|
148
|
+
"JSON.stringify": { arity: 1, emit: (args) => `bf_json ${wrapGoArg(args[0])}` },
|
|
149
|
+
String: { arity: 1, emit: (args) => `bf_string ${wrapGoArg(args[0])}` },
|
|
150
|
+
Number: { arity: 1, emit: (args) => `bf_number ${wrapGoArg(args[0])}` },
|
|
151
|
+
"Math.floor": { arity: 1, emit: (args) => `bf_floor ${wrapGoArg(args[0])}` },
|
|
152
|
+
"Math.ceil": { arity: 1, emit: (args) => `bf_ceil ${wrapGoArg(args[0])}` },
|
|
153
|
+
"Math.round": { arity: 1, emit: (args) => `bf_round ${wrapGoArg(args[0])}` },
|
|
154
|
+
"Math.min": { arity: 2, emit: (args) => `bf_min ${wrapGoArg(args[0])} ${wrapGoArg(args[1])}` },
|
|
155
|
+
"Math.max": { arity: 2, emit: (args) => `bf_max ${wrapGoArg(args[0])} ${wrapGoArg(args[1])}` }
|
|
137
156
|
};
|
|
138
157
|
|
|
139
158
|
class GoTemplateAdapter extends BaseAdapter {
|
|
@@ -148,10 +167,12 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
148
167
|
componentName = "";
|
|
149
168
|
options;
|
|
150
169
|
inLoop = false;
|
|
170
|
+
pendingChildrenDefines = [];
|
|
151
171
|
loopParamStack = [];
|
|
152
172
|
loopVarRefCount = new Map;
|
|
153
173
|
loopBindingStack = [];
|
|
154
174
|
errors = [];
|
|
175
|
+
currentMemos = [];
|
|
155
176
|
propsObjectName = null;
|
|
156
177
|
restPropsName = null;
|
|
157
178
|
templateVarCounter = 0;
|
|
@@ -159,6 +180,7 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
159
180
|
localTypeAliases = new Map;
|
|
160
181
|
localStructFields = new Map;
|
|
161
182
|
synthStructTypes = new Map;
|
|
183
|
+
currentTypeDefinitions = [];
|
|
162
184
|
usesHtmlTemplate = false;
|
|
163
185
|
rootScopeNodes = new Set;
|
|
164
186
|
usesFmt = false;
|
|
@@ -166,6 +188,11 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
166
188
|
moduleStringConsts = new Map;
|
|
167
189
|
localConstants = [];
|
|
168
190
|
contextConsumers = [];
|
|
191
|
+
searchParamsLocals = new Set;
|
|
192
|
+
localHelperNames = new Set;
|
|
193
|
+
needsStringsImport = false;
|
|
194
|
+
referencedDerivedConsts = new Set;
|
|
195
|
+
memoBackedLoopSlice = new Map;
|
|
169
196
|
childContextConsumers = new Map;
|
|
170
197
|
nillablePropNames = new Set;
|
|
171
198
|
constructor(options = {}) {
|
|
@@ -179,12 +206,18 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
179
206
|
generate(ir, options) {
|
|
180
207
|
this.componentName = ir.metadata.componentName;
|
|
181
208
|
this.errors = [];
|
|
209
|
+
this.referencedDerivedConsts = new Set;
|
|
182
210
|
this.templateVarCounter = 0;
|
|
211
|
+
this.pendingChildrenDefines = [];
|
|
183
212
|
this.propsObjectName = ir.metadata.propsObjectName;
|
|
184
213
|
this.restPropsName = ir.metadata.restPropsName ?? null;
|
|
185
214
|
this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
|
|
186
215
|
this.localConstants = ir.metadata.localConstants ?? [];
|
|
216
|
+
this.localHelperNames = new Set(this.localConstants.filter((c) => !c.isModule && c.containsArrow).map((c) => c.name));
|
|
217
|
+
this.currentMemos = ir.metadata.memos ?? [];
|
|
218
|
+
this.currentTypeDefinitions = ir.metadata.typeDefinitions ?? [];
|
|
187
219
|
this.contextConsumers = collectContextConsumers(ir.metadata);
|
|
220
|
+
this.searchParamsLocals = searchParamsLocalNames(ir.metadata);
|
|
188
221
|
augmentInheritedPropAccesses(ir);
|
|
189
222
|
this.nillablePropNames = this.collectNillablePropNames(ir);
|
|
190
223
|
if (!options?.siblingTemplatesRegistered) {
|
|
@@ -194,12 +227,22 @@ class GoTemplateAdapter extends BaseAdapter {
|
|
|
194
227
|
const isRootComponent = ir.root.type === "component";
|
|
195
228
|
const isIfStatement = ir.root.type === "if-statement";
|
|
196
229
|
this.rootScopeNodes = collectRootScopeNodes(ir.root);
|
|
230
|
+
this.memoBackedLoopSlice = new Map;
|
|
231
|
+
for (const nested of this.findNestedComponents(ir.root)) {
|
|
232
|
+
const memoName = this.extractMemoNameFromLoopArray(nested.loopArray);
|
|
233
|
+
if (memoName)
|
|
234
|
+
this.memoBackedLoopSlice.set(memoName, `${nested.name}s`);
|
|
235
|
+
}
|
|
197
236
|
const templateBody = isIfStatement ? this.renderIfStatement(ir.root, { isRootOfClientComponent: hasInteractivity }) : this.renderNode(ir.root, { isRootOfClientComponent: hasInteractivity && isRootComponent });
|
|
198
237
|
const scriptRegistrations = options?.skipScriptRegistration ? "" : this.generateScriptRegistrations(ir, options?.scriptBaseName);
|
|
199
|
-
|
|
238
|
+
let template = `{{define "${this.componentName}"}}
|
|
200
239
|
${scriptRegistrations}${templateBody}
|
|
201
240
|
{{end}}
|
|
202
241
|
`;
|
|
242
|
+
for (const d of this.pendingChildrenDefines) {
|
|
243
|
+
template += `{{define "${d.name}"}}${d.content}{{end}}
|
|
244
|
+
`;
|
|
245
|
+
}
|
|
203
246
|
const types = this.generateTypes(ir);
|
|
204
247
|
if (this.errors.length > 0) {
|
|
205
248
|
ir.errors.push(...this.errors);
|
|
@@ -436,10 +479,15 @@ ${scriptRegistrations}${templateBody}
|
|
|
436
479
|
this.usesHtmlTemplate = false;
|
|
437
480
|
this.usesFmt = false;
|
|
438
481
|
this.propsObjectName = ir.metadata.propsObjectName;
|
|
482
|
+
this.restPropsName = ir.metadata.restPropsName ?? null;
|
|
439
483
|
augmentInheritedPropAccesses(ir);
|
|
440
484
|
this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
|
|
441
485
|
this.localConstants = ir.metadata.localConstants ?? [];
|
|
486
|
+
this.localHelperNames = new Set(this.localConstants.filter((c) => !c.isModule && c.containsArrow).map((c) => c.name));
|
|
487
|
+
this.currentMemos = ir.metadata.memos ?? [];
|
|
488
|
+
this.currentTypeDefinitions = ir.metadata.typeDefinitions ?? [];
|
|
442
489
|
this.contextConsumers = collectContextConsumers(ir.metadata);
|
|
490
|
+
this.searchParamsLocals = searchParamsLocalNames(ir.metadata);
|
|
443
491
|
const lines = [];
|
|
444
492
|
const componentName = ir.metadata.componentName;
|
|
445
493
|
this.localTypeNames = new Set;
|
|
@@ -492,11 +540,39 @@ ${goFields.join(`
|
|
|
492
540
|
lines.push("");
|
|
493
541
|
}
|
|
494
542
|
const nestedComponents = this.findNestedComponents(ir.root);
|
|
543
|
+
for (const nested of nestedComponents) {
|
|
544
|
+
if (nested.loopItemType || !nested.loopArray)
|
|
545
|
+
continue;
|
|
546
|
+
const memoName = this.extractMemoNameFromLoopArray(nested.loopArray);
|
|
547
|
+
if (memoName) {
|
|
548
|
+
const memo = ir.metadata.memos.find((m) => m.name === memoName);
|
|
549
|
+
if (memo) {
|
|
550
|
+
const blockReturn = this.resolveBlockBodyMemoModuleConst(memo.computation, ir.metadata.signals);
|
|
551
|
+
if (blockReturn) {
|
|
552
|
+
const constant = (ir.metadata.localConstants ?? []).find((c) => c.name === blockReturn.constName && c.origin?.scope === "module");
|
|
553
|
+
if (constant?.type?.elementType) {
|
|
554
|
+
nested.loopItemType = constant.type.elementType;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
const directConst = (ir.metadata.localConstants ?? []).find((c) => c.name === nested.loopArray && c.origin?.scope === "module");
|
|
561
|
+
if (directConst?.type?.elementType) {
|
|
562
|
+
nested.loopItemType = directConst.type.elementType;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
for (const nested of nestedComponents) {
|
|
566
|
+
if (!nested.bodyChildren || nested.bodyChildren.length === 0)
|
|
567
|
+
continue;
|
|
568
|
+
this.generateLoopBodyWrapperStruct(lines, componentName, nested);
|
|
569
|
+
}
|
|
495
570
|
const propTypeOverrides = this.buildPropTypeOverrides(ir);
|
|
496
571
|
const spreadSlots = this.collectSpreadSlots(ir.root);
|
|
497
572
|
this.generateInputStruct(lines, ir, componentName, nestedComponents, propTypeOverrides, spreadSlots);
|
|
573
|
+
this.needsStringsImport = false;
|
|
498
574
|
this.generatePropsStruct(lines, ir, componentName, nestedComponents, propTypeOverrides, spreadSlots);
|
|
499
|
-
this.generateNewPropsFunction(lines, ir, componentName, nestedComponents, spreadSlots);
|
|
575
|
+
this.generateNewPropsFunction(lines, ir, componentName, nestedComponents, spreadSlots, propTypeOverrides);
|
|
500
576
|
const header = [];
|
|
501
577
|
header.push(`package ${this.options.packageName}`);
|
|
502
578
|
header.push("");
|
|
@@ -506,6 +582,8 @@ ${goFields.join(`
|
|
|
506
582
|
if (this.usesHtmlTemplate)
|
|
507
583
|
header.push('\t"html/template"');
|
|
508
584
|
header.push('\t"math/rand"');
|
|
585
|
+
if (this.needsStringsImport)
|
|
586
|
+
header.push('\t"strings"');
|
|
509
587
|
header.push("");
|
|
510
588
|
header.push('\tbf "github.com/barefootjs/runtime/bf"');
|
|
511
589
|
header.push(")");
|
|
@@ -676,6 +754,20 @@ ${goFields.join(`
|
|
|
676
754
|
}
|
|
677
755
|
return nillable;
|
|
678
756
|
}
|
|
757
|
+
usesSearchParams(ir) {
|
|
758
|
+
if (this.searchParamsLocals.size === 0)
|
|
759
|
+
return false;
|
|
760
|
+
const taken = new Set([
|
|
761
|
+
...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
|
|
762
|
+
...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
|
|
763
|
+
...ir.metadata.memos.map((m) => this.capitalizeFieldName(m.name)),
|
|
764
|
+
...this.contextConsumers.map((c) => this.contextFieldName(c))
|
|
765
|
+
]);
|
|
766
|
+
if (ir.metadata.restPropsName) {
|
|
767
|
+
taken.add(this.capitalizeFieldName(ir.metadata.restPropsName));
|
|
768
|
+
}
|
|
769
|
+
return !taken.has("SearchParams");
|
|
770
|
+
}
|
|
679
771
|
generateInputStruct(lines, ir, componentName, nestedComponents, propTypeOverrides, spreadSlots) {
|
|
680
772
|
const inputTypeName = `${componentName}Input`;
|
|
681
773
|
lines.push(`// ${inputTypeName} is the user-facing input type.`);
|
|
@@ -683,6 +775,9 @@ ${goFields.join(`
|
|
|
683
775
|
lines.push("\tScopeID string // Optional: if empty, random ID is generated");
|
|
684
776
|
lines.push("\tBfParent string // Optional: parent scope id");
|
|
685
777
|
lines.push("\tBfMount string // Optional: slot id in parent");
|
|
778
|
+
if (this.usesSearchParams(ir)) {
|
|
779
|
+
lines.push("\tSearchParams bf.SearchParams // Optional: request query for searchParams()");
|
|
780
|
+
}
|
|
686
781
|
const inputNested = nestedComponents.filter((n) => !n.isDynamic || n.isPropDerived);
|
|
687
782
|
const nestedArrayFields = new Set(nestedComponents.map((n) => `${n.name}s`));
|
|
688
783
|
for (const param of ir.metadata.propsParams) {
|
|
@@ -727,6 +822,9 @@ ${goFields.join(`
|
|
|
727
822
|
lines.push('\tBfMount string `json:"-"`');
|
|
728
823
|
lines.push('\tBfDataKey string `json:"-"`');
|
|
729
824
|
lines.push('\tScripts *bf.ScriptCollector `json:"-"`');
|
|
825
|
+
if (this.usesSearchParams(ir)) {
|
|
826
|
+
lines.push('\tSearchParams bf.SearchParams `json:"-"`');
|
|
827
|
+
}
|
|
730
828
|
const nestedArrayFields = new Set(nestedComponents.map((n) => `${n.name}s`));
|
|
731
829
|
const propFieldNames = new Set;
|
|
732
830
|
for (const param of ir.metadata.propsParams) {
|
|
@@ -773,10 +871,20 @@ ${goFields.join(`
|
|
|
773
871
|
}
|
|
774
872
|
for (const memo of ir.metadata.memos) {
|
|
775
873
|
const fieldName = this.capitalizeFieldName(memo.name);
|
|
874
|
+
if (propFieldNames.has(fieldName))
|
|
875
|
+
continue;
|
|
776
876
|
const jsonTag = this.toJsonTag(memo.name);
|
|
777
877
|
const goType = this.inferMemoType(memo, ir.metadata.signals, propsParamMap);
|
|
778
878
|
lines.push(` ${fieldName} ${goType} \`json:"${jsonTag}"\``);
|
|
779
879
|
}
|
|
880
|
+
const takenForDerivedConsts = new Set([
|
|
881
|
+
...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
|
|
882
|
+
...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
|
|
883
|
+
...ir.metadata.memos.map((m) => this.capitalizeFieldName(m.name))
|
|
884
|
+
]);
|
|
885
|
+
for (const f of this.computeDerivedConstFields(takenForDerivedConsts)) {
|
|
886
|
+
lines.push(` ${f.name} string \`json:"-"\``);
|
|
887
|
+
}
|
|
780
888
|
const takenProps = new Set([
|
|
781
889
|
...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
|
|
782
890
|
...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
|
|
@@ -787,11 +895,12 @@ ${goFields.join(`
|
|
|
787
895
|
lines.push(` ${this.contextFieldName(c)} ${this.contextConsumerGoType(c)} \`json:"${jsonTag}"\``);
|
|
788
896
|
}
|
|
789
897
|
for (const nested of nestedComponents) {
|
|
898
|
+
const elemType = nested.bodyChildren?.length ? this.loopBodyWrapperName(componentName, nested) : `${nested.name}Props`;
|
|
790
899
|
if (nested.isDynamic && !nested.isPropDerived) {
|
|
791
|
-
lines.push(` ${nested.name}s []${
|
|
900
|
+
lines.push(` ${nested.name}s []${elemType} \`json:"-"\``);
|
|
792
901
|
} else {
|
|
793
902
|
const jsonTag = this.toJsonTag(`${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`);
|
|
794
|
-
lines.push(` ${nested.name}s []${
|
|
903
|
+
lines.push(` ${nested.name}s []${elemType} \`json:"${jsonTag}"\``);
|
|
795
904
|
}
|
|
796
905
|
}
|
|
797
906
|
const staticChildren = this.collectStaticChildInstances(ir.root);
|
|
@@ -805,10 +914,73 @@ ${goFields.join(`
|
|
|
805
914
|
lines.push("}");
|
|
806
915
|
lines.push("");
|
|
807
916
|
}
|
|
808
|
-
|
|
917
|
+
generateLoopBodyWrapperStruct(lines, parentComponentName, nested) {
|
|
918
|
+
const wrapperName = this.loopBodyWrapperName(parentComponentName, nested);
|
|
919
|
+
const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
|
|
920
|
+
const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
|
|
921
|
+
lines.push(`// ${wrapperName} wraps ${nested.name}Props with per-row loop datum`);
|
|
922
|
+
lines.push(`// fields and child component slots for the loop body children. (#1897)`);
|
|
923
|
+
lines.push(`type ${wrapperName} struct {`);
|
|
924
|
+
lines.push(` ${nested.name}Props`);
|
|
925
|
+
for (const f of datumFields) {
|
|
926
|
+
lines.push(` ${f.goName} ${f.goType} \`json:"-"\``);
|
|
927
|
+
}
|
|
928
|
+
for (const child of bodyChildInstances) {
|
|
929
|
+
lines.push(` ${child.fieldName} ${child.name}Props \`json:"-"\``);
|
|
930
|
+
}
|
|
931
|
+
lines.push("}");
|
|
932
|
+
lines.push("");
|
|
933
|
+
}
|
|
934
|
+
extractMemoNameFromLoopArray(loopArray) {
|
|
935
|
+
if (!loopArray)
|
|
936
|
+
return null;
|
|
937
|
+
const match = loopArray.match(/^(\w+)\(\)$/);
|
|
938
|
+
return match ? match[1] : null;
|
|
939
|
+
}
|
|
940
|
+
loopBodyWrapperName(parentName, nested) {
|
|
941
|
+
return `${parentName}${nested.name}L${nested.loopMarkerId ?? "0"}Ctx`;
|
|
942
|
+
}
|
|
943
|
+
resolveLoopDatumFields(itemType) {
|
|
944
|
+
if (!itemType)
|
|
945
|
+
return [];
|
|
946
|
+
const typeName = itemType.raw?.replace(/\[\]$/, "") ?? itemType.raw;
|
|
947
|
+
if (!typeName)
|
|
948
|
+
return [];
|
|
949
|
+
for (const td of this.currentTypeDefinitions) {
|
|
950
|
+
if (td.name === typeName) {
|
|
951
|
+
const fields = [];
|
|
952
|
+
for (const prop of td.properties ?? []) {
|
|
953
|
+
if (!GoTemplateAdapter.GO_IDENTIFIER.test(prop.name))
|
|
954
|
+
continue;
|
|
955
|
+
fields.push({
|
|
956
|
+
tsName: prop.name,
|
|
957
|
+
goName: this.capitalizeFieldName(prop.name),
|
|
958
|
+
goType: this.typeInfoToGo(prop.type)
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
if (fields.length > 0)
|
|
962
|
+
return fields;
|
|
963
|
+
break;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
const structFields = this.localStructFields.get(typeName);
|
|
967
|
+
if (structFields) {
|
|
968
|
+
return Array.from(structFields, ([tsName, goName]) => ({ tsName, goName, goType: "interface{}" }));
|
|
969
|
+
}
|
|
970
|
+
return [];
|
|
971
|
+
}
|
|
972
|
+
collectBodyChildInstances(bodyChildren) {
|
|
973
|
+
const result = [];
|
|
974
|
+
for (const child of bodyChildren) {
|
|
975
|
+
this.collectStaticChildInstancesRecursive(child, result, false, new Map);
|
|
976
|
+
}
|
|
977
|
+
return result;
|
|
978
|
+
}
|
|
979
|
+
generateNewPropsFunction(lines, ir, componentName, nestedComponents, spreadSlots, propTypeOverrides) {
|
|
809
980
|
const inputTypeName = `${componentName}Input`;
|
|
810
981
|
const propsTypeName = `${componentName}Props`;
|
|
811
|
-
const
|
|
982
|
+
const dynamicWithBody = nestedComponents.filter((n) => n.isDynamic && !n.isPropDerived && n.bodyChildren && n.bodyChildren.length > 0);
|
|
983
|
+
const signalDynamicNested = nestedComponents.filter((n) => n.isDynamic && !n.isPropDerived && !(n.bodyChildren && n.bodyChildren.length > 0));
|
|
812
984
|
lines.push(`// New${componentName}Props creates ${propsTypeName} from ${inputTypeName}.`);
|
|
813
985
|
for (const nested of signalDynamicNested) {
|
|
814
986
|
const arrayField = `${nested.name}s`;
|
|
@@ -833,22 +1005,77 @@ ${goFields.join(`
|
|
|
833
1005
|
lines.push("\t}");
|
|
834
1006
|
lines.push("");
|
|
835
1007
|
const staticNested = nestedComponents.filter((n) => !n.isDynamic || n.isPropDerived);
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
|
|
1008
|
+
const emittedWrapperVars = new Set;
|
|
1009
|
+
const staticWithBody = staticNested.filter((n) => n.bodyChildren && n.bodyChildren.length > 0);
|
|
1010
|
+
const staticWithoutBody = staticNested.filter((n) => !n.bodyChildren || n.bodyChildren.length === 0);
|
|
1011
|
+
for (const nested of staticWithoutBody) {
|
|
1012
|
+
const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
|
|
1013
|
+
lines.push(` ${varName} := make([]${nested.name}Props, len(in.${nested.name}s))`);
|
|
1014
|
+
lines.push(` for i, item := range in.${nested.name}s {`);
|
|
1015
|
+
lines.push(` ${varName}[i] = New${nested.name}Props(item)`);
|
|
1016
|
+
lines.push(` ${varName}[i].BfParent = scopeID`);
|
|
1017
|
+
lines.push(` ${varName}[i].BfMount = "${nested.slotId}"`);
|
|
1018
|
+
const keyField = loopKeyToGoFieldPath(nested.loopKey, nested.loopParam);
|
|
1019
|
+
if (keyField) {
|
|
1020
|
+
lines.push(` ${varName}[i].BfDataKey = fmt.Sprint(${keyField})`);
|
|
1021
|
+
this.usesFmt = true;
|
|
1022
|
+
}
|
|
1023
|
+
lines.push("\t}");
|
|
1024
|
+
lines.push("");
|
|
1025
|
+
}
|
|
1026
|
+
for (const nested of staticWithBody) {
|
|
1027
|
+
const loopArray = nested.loopArray;
|
|
1028
|
+
const moduleConst = loopArray ? (ir.metadata.localConstants ?? []).find((c) => c.name === loopArray && c.origin?.scope === "module" && c.value && c.type) : null;
|
|
1029
|
+
const bakedValue = moduleConst?.type ? this.convertInitialValue(moduleConst.value, moduleConst.type, ir.metadata.propsParams) : null;
|
|
1030
|
+
if (!bakedValue || bakedValue === "nil" || bakedValue === "0")
|
|
1031
|
+
continue;
|
|
1032
|
+
const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
|
|
1033
|
+
const wrapperType = this.loopBodyWrapperName(componentName, nested);
|
|
1034
|
+
const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
|
|
1035
|
+
const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
|
|
1036
|
+
for (const child of bodyChildInstances) {
|
|
1037
|
+
const childVar = `child_${child.fieldName}`;
|
|
1038
|
+
lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
|
|
1039
|
+
lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
|
|
1040
|
+
lines.push(` BfParent: scopeID,`);
|
|
1041
|
+
lines.push(` BfMount: "${child.slotId}",`);
|
|
1042
|
+
for (const prop of child.props) {
|
|
1043
|
+
if (prop.value.kind === "literal") {
|
|
1044
|
+
lines.push(` ${this.capitalizeFieldName(prop.name)}: ${this.goLiteral(prop.value.value)},`);
|
|
1045
|
+
} else if (prop.value.kind === "boolean-shorthand" || prop.value.kind === "boolean-attr") {
|
|
1046
|
+
lines.push(` ${this.capitalizeFieldName(prop.name)}: true,`);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
lines.push(` })`);
|
|
851
1050
|
}
|
|
1051
|
+
if (bodyChildInstances.length > 0)
|
|
1052
|
+
lines.push("");
|
|
1053
|
+
const dataVar = `${varName}Data`;
|
|
1054
|
+
lines.push(` ${dataVar} := ${bakedValue}`);
|
|
1055
|
+
lines.push(` ${varName} := make([]${wrapperType}, len(${dataVar}))`);
|
|
1056
|
+
lines.push(` for i, item := range ${dataVar} {`);
|
|
1057
|
+
lines.push(` ${varName}[i] = ${wrapperType}{`);
|
|
1058
|
+
lines.push(` ${nested.name}Props: New${nested.name}Props(${nested.name}Input{`);
|
|
1059
|
+
lines.push(` BfParent: scopeID,`);
|
|
1060
|
+
lines.push(` BfMount: "${nested.slotId}",`);
|
|
1061
|
+
lines.push(` }),`);
|
|
1062
|
+
for (const f of datumFields) {
|
|
1063
|
+
lines.push(` ${f.goName}: item.${f.goName},`);
|
|
1064
|
+
}
|
|
1065
|
+
for (const child of bodyChildInstances) {
|
|
1066
|
+
lines.push(` ${child.fieldName}: child_${child.fieldName},`);
|
|
1067
|
+
}
|
|
1068
|
+
lines.push(` }`);
|
|
1069
|
+
lines.push(` ${varName}[i].BfParent = scopeID`);
|
|
1070
|
+
lines.push(` ${varName}[i].BfMount = "${nested.slotId}"`);
|
|
1071
|
+
const keyField = loopKeyToGoFieldPath(nested.loopKey, nested.loopParam);
|
|
1072
|
+
if (keyField) {
|
|
1073
|
+
lines.push(` ${varName}[i].BfDataKey = fmt.Sprint(${keyField})`);
|
|
1074
|
+
this.usesFmt = true;
|
|
1075
|
+
}
|
|
1076
|
+
lines.push("\t}");
|
|
1077
|
+
lines.push("");
|
|
1078
|
+
emittedWrapperVars.add(varName);
|
|
852
1079
|
}
|
|
853
1080
|
const propFallbackVars = this.collectPropFallbackVars(ir);
|
|
854
1081
|
for (const [, info] of propFallbackVars) {
|
|
@@ -859,11 +1086,87 @@ ${goFields.join(`
|
|
|
859
1086
|
}
|
|
860
1087
|
if (propFallbackVars.size > 0)
|
|
861
1088
|
lines.push("");
|
|
1089
|
+
const propsParamMap = new Map(ir.metadata.propsParams.map((p) => [p.name, p]));
|
|
1090
|
+
for (const nested of dynamicWithBody) {
|
|
1091
|
+
const memoName = this.extractMemoNameFromLoopArray(nested.loopArray);
|
|
1092
|
+
if (!memoName)
|
|
1093
|
+
continue;
|
|
1094
|
+
const memo = ir.metadata.memos.find((m) => m.name === memoName);
|
|
1095
|
+
if (!memo)
|
|
1096
|
+
continue;
|
|
1097
|
+
const goType = this.inferMemoType(memo, ir.metadata.signals, propsParamMap);
|
|
1098
|
+
const bakedValue = this.computeMemoInitialValue(memo, ir.metadata.signals, ir.metadata.propsParams, propFallbackVars, goType);
|
|
1099
|
+
if (bakedValue === "nil" || bakedValue === "0")
|
|
1100
|
+
continue;
|
|
1101
|
+
const wrapperType = this.loopBodyWrapperName(componentName, nested);
|
|
1102
|
+
const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
|
|
1103
|
+
const datumFields = this.resolveLoopDatumFields(nested.loopItemType);
|
|
1104
|
+
const bodyChildInstances = this.collectBodyChildInstances(nested.bodyChildren);
|
|
1105
|
+
for (const child of bodyChildInstances) {
|
|
1106
|
+
const childVar = `child_${child.fieldName}`;
|
|
1107
|
+
lines.push(` ${childVar} := New${child.name}Props(${child.name}Input{`);
|
|
1108
|
+
lines.push(` ScopeID: scopeID + "_${child.slotId}",`);
|
|
1109
|
+
lines.push(` BfParent: scopeID,`);
|
|
1110
|
+
lines.push(` BfMount: "${child.slotId}",`);
|
|
1111
|
+
for (const prop of child.props) {
|
|
1112
|
+
if (prop.value.kind === "literal") {
|
|
1113
|
+
lines.push(` ${this.capitalizeFieldName(prop.name)}: ${this.goLiteral(prop.value.value)},`);
|
|
1114
|
+
} else if (prop.value.kind === "boolean-shorthand" || prop.value.kind === "boolean-attr") {
|
|
1115
|
+
lines.push(` ${this.capitalizeFieldName(prop.name)}: true,`);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
lines.push(` })`);
|
|
1119
|
+
}
|
|
1120
|
+
if (bodyChildInstances.length > 0)
|
|
1121
|
+
lines.push("");
|
|
1122
|
+
lines.push(` bakedData := ${bakedValue}`);
|
|
1123
|
+
lines.push(` ${varName} := make([]${wrapperType}, len(bakedData))`);
|
|
1124
|
+
lines.push(` for i, item := range bakedData {`);
|
|
1125
|
+
lines.push(` ${varName}[i] = ${wrapperType}{`);
|
|
1126
|
+
lines.push(` ${nested.name}Props: New${nested.name}Props(${nested.name}Input{`);
|
|
1127
|
+
lines.push(` BfParent: scopeID,`);
|
|
1128
|
+
lines.push(` BfMount: "${nested.slotId}",`);
|
|
1129
|
+
lines.push(` }),`);
|
|
1130
|
+
for (const f of datumFields) {
|
|
1131
|
+
lines.push(` ${f.goName}: item.${f.goName},`);
|
|
1132
|
+
}
|
|
1133
|
+
for (const child of bodyChildInstances) {
|
|
1134
|
+
lines.push(` ${child.fieldName}: child_${child.fieldName},`);
|
|
1135
|
+
}
|
|
1136
|
+
lines.push(` }`);
|
|
1137
|
+
const keyField = loopKeyToGoFieldPath(nested.loopKey, nested.loopParam);
|
|
1138
|
+
if (keyField) {
|
|
1139
|
+
lines.push(` ${varName}[i].BfDataKey = fmt.Sprint(${keyField})`);
|
|
1140
|
+
this.usesFmt = true;
|
|
1141
|
+
}
|
|
1142
|
+
lines.push(` }`);
|
|
1143
|
+
lines.push("");
|
|
1144
|
+
emittedWrapperVars.add(varName);
|
|
1145
|
+
}
|
|
862
1146
|
lines.push(` return ${propsTypeName}{`);
|
|
863
1147
|
lines.push("\t\tScopeID: scopeID,");
|
|
864
1148
|
lines.push("\t\tBfParent: in.BfParent,");
|
|
865
1149
|
lines.push("\t\tBfMount: in.BfMount,");
|
|
1150
|
+
if (this.usesSearchParams(ir)) {
|
|
1151
|
+
lines.push("\t\tSearchParams: in.SearchParams,");
|
|
1152
|
+
}
|
|
866
1153
|
const nestedArrayFields = new Set(nestedComponents.map((n) => `${n.name}s`));
|
|
1154
|
+
const memoFallbacks = new Map;
|
|
1155
|
+
for (const memo of ir.metadata.memos) {
|
|
1156
|
+
const stripped = memo.computation.replace(/^\(\)\s*=>\s*/, "");
|
|
1157
|
+
const m = this.extractPropFallback(stripped);
|
|
1158
|
+
if (!m)
|
|
1159
|
+
continue;
|
|
1160
|
+
if (this.capitalizeFieldName(m.propName) !== this.capitalizeFieldName(memo.name))
|
|
1161
|
+
continue;
|
|
1162
|
+
const param = ir.metadata.propsParams.find((p) => this.capitalizeFieldName(p.name) === this.capitalizeFieldName(memo.name));
|
|
1163
|
+
if (!param)
|
|
1164
|
+
continue;
|
|
1165
|
+
const goType = this.resolvePropGoType(param, propTypeOverrides);
|
|
1166
|
+
if (goType !== "string" && goType !== "interface{}")
|
|
1167
|
+
continue;
|
|
1168
|
+
memoFallbacks.set(this.capitalizeFieldName(memo.name), { goFallback: m.goFallback, goType });
|
|
1169
|
+
}
|
|
867
1170
|
const propFieldNames = new Set;
|
|
868
1171
|
for (const param of ir.metadata.propsParams) {
|
|
869
1172
|
const fieldName = this.capitalizeFieldName(param.name);
|
|
@@ -873,9 +1176,14 @@ ${goFields.join(`
|
|
|
873
1176
|
if (hoisted) {
|
|
874
1177
|
lines.push(` ${fieldName}: ${hoisted.varName},`);
|
|
875
1178
|
} else {
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
|
|
1179
|
+
const paramDefault = this.goPropDefault(param.defaultValue);
|
|
1180
|
+
const memoFold = memoFallbacks.get(fieldName);
|
|
1181
|
+
if (paramDefault !== null) {
|
|
1182
|
+
lines.push(` ${fieldName}: ${this.applyGoFallback(`in.${fieldName}`, paramDefault)},`);
|
|
1183
|
+
} else if (memoFold !== undefined && memoFold.goType === "string") {
|
|
1184
|
+
lines.push(` ${fieldName}: ${this.applyGoFallback(`in.${fieldName}`, memoFold.goFallback)},`);
|
|
1185
|
+
} else if (memoFold !== undefined) {
|
|
1186
|
+
lines.push(` ${fieldName}: func() interface{} { v := interface{}(in.${fieldName}); if v == nil || v == "" { return ${memoFold.goFallback} }; return v }(),`);
|
|
879
1187
|
} else {
|
|
880
1188
|
lines.push(` ${fieldName}: in.${fieldName},`);
|
|
881
1189
|
}
|
|
@@ -896,17 +1204,33 @@ ${goFields.join(`
|
|
|
896
1204
|
lines.push(` ${fieldName}: ${initialValue},`);
|
|
897
1205
|
}
|
|
898
1206
|
}
|
|
899
|
-
for (const nested of
|
|
1207
|
+
for (const nested of staticWithoutBody) {
|
|
1208
|
+
const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
|
|
1209
|
+
lines.push(` ${nested.name}s: ${varName},`);
|
|
1210
|
+
}
|
|
1211
|
+
for (const nested of [...staticWithBody, ...dynamicWithBody]) {
|
|
900
1212
|
const varName = `${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`;
|
|
1213
|
+
if (!emittedWrapperVars.has(varName))
|
|
1214
|
+
continue;
|
|
901
1215
|
lines.push(` ${nested.name}s: ${varName},`);
|
|
902
1216
|
}
|
|
903
1217
|
const memoPropsParamMap = new Map(ir.metadata.propsParams.map((p) => [p.name, p]));
|
|
904
1218
|
for (const memo of ir.metadata.memos) {
|
|
905
1219
|
const fieldName = this.capitalizeFieldName(memo.name);
|
|
1220
|
+
if (propFieldNames.has(fieldName))
|
|
1221
|
+
continue;
|
|
906
1222
|
const goType = this.inferMemoType(memo, ir.metadata.signals, memoPropsParamMap);
|
|
907
1223
|
const memoValue = this.computeMemoInitialValue(memo, ir.metadata.signals, ir.metadata.propsParams, propFallbackVars, goType);
|
|
908
1224
|
lines.push(` ${fieldName}: ${memoValue},`);
|
|
909
1225
|
}
|
|
1226
|
+
const takenDerivedInit = new Set([
|
|
1227
|
+
...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
|
|
1228
|
+
...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
|
|
1229
|
+
...ir.metadata.memos.map((m) => this.capitalizeFieldName(m.name))
|
|
1230
|
+
]);
|
|
1231
|
+
for (const f of this.computeDerivedConstFields(takenDerivedInit)) {
|
|
1232
|
+
lines.push(` ${f.name}: ${f.init},`);
|
|
1233
|
+
}
|
|
910
1234
|
const takenInit = new Set([
|
|
911
1235
|
...ir.metadata.propsParams.map((p) => this.capitalizeFieldName(p.name)),
|
|
912
1236
|
...ir.metadata.signals.map((s) => this.capitalizeFieldName(s.getter)),
|
|
@@ -939,6 +1263,8 @@ ${goFields.join(`
|
|
|
939
1263
|
restBagEntries.push(`${JSON.stringify(jsxName)}: ${goValue}`);
|
|
940
1264
|
return;
|
|
941
1265
|
}
|
|
1266
|
+
if (jsxName.includes("-"))
|
|
1267
|
+
return;
|
|
942
1268
|
lines.push(` ${this.capitalizeFieldName(jsxName)}: ${goValue},`);
|
|
943
1269
|
};
|
|
944
1270
|
for (const prop of child.props) {
|
|
@@ -1020,12 +1346,17 @@ ${goFields.join(`
|
|
|
1020
1346
|
const loop = node;
|
|
1021
1347
|
if (loop.childComponent) {
|
|
1022
1348
|
if (!result.some((c) => c.name === loop.childComponent.name)) {
|
|
1349
|
+
const hasBodyChildren = loop.childComponent.children.length > 0;
|
|
1023
1350
|
result.push({
|
|
1024
1351
|
...loop.childComponent,
|
|
1025
1352
|
isDynamic: !loop.isStaticArray,
|
|
1026
1353
|
isPropDerived: !!loop.isPropDerivedArray,
|
|
1027
1354
|
loopKey: loop.key ?? undefined,
|
|
1028
|
-
loopParam: loop.param ?? undefined
|
|
1355
|
+
loopParam: loop.param ?? undefined,
|
|
1356
|
+
bodyChildren: hasBodyChildren ? loop.childComponent.children : undefined,
|
|
1357
|
+
loopArray: loop.array,
|
|
1358
|
+
loopMarkerId: loop.markerId,
|
|
1359
|
+
loopItemType: loop.itemType
|
|
1029
1360
|
});
|
|
1030
1361
|
}
|
|
1031
1362
|
}
|
|
@@ -1048,6 +1379,17 @@ ${goFields.join(`
|
|
|
1048
1379
|
if (cond.whenFalse) {
|
|
1049
1380
|
this.collectNestedComponents(cond.whenFalse, result);
|
|
1050
1381
|
}
|
|
1382
|
+
} else if (node.type === "if-statement") {
|
|
1383
|
+
const stmt = node;
|
|
1384
|
+
this.collectNestedComponents(stmt.consequent, result);
|
|
1385
|
+
if (stmt.alternate) {
|
|
1386
|
+
this.collectNestedComponents(stmt.alternate, result);
|
|
1387
|
+
}
|
|
1388
|
+
} else if (node.type === "component") {
|
|
1389
|
+
const comp = node;
|
|
1390
|
+
for (const child of comp.children) {
|
|
1391
|
+
this.collectNestedComponents(child, result);
|
|
1392
|
+
}
|
|
1051
1393
|
}
|
|
1052
1394
|
}
|
|
1053
1395
|
collectStaticChildInstances(node) {
|
|
@@ -1119,6 +1461,9 @@ ${goFields.join(`
|
|
|
1119
1461
|
childrenScopedHtmlExpr: this.extractScopedHtmlChildren(effectiveChildren),
|
|
1120
1462
|
contextBindings: providerCtx.size > 0 ? providerCtx : undefined
|
|
1121
1463
|
});
|
|
1464
|
+
for (const child of effectiveChildren) {
|
|
1465
|
+
this.collectStaticChildInstancesRecursive(child, result, inLoop, providerCtx);
|
|
1466
|
+
}
|
|
1122
1467
|
}
|
|
1123
1468
|
if (comp.name === "Portal" && comp.children) {
|
|
1124
1469
|
for (const child of comp.children) {
|
|
@@ -1146,6 +1491,12 @@ ${goFields.join(`
|
|
|
1146
1491
|
if (cond.whenFalse) {
|
|
1147
1492
|
this.collectStaticChildInstancesRecursive(cond.whenFalse, result, inLoop, providerCtx);
|
|
1148
1493
|
}
|
|
1494
|
+
} else if (node.type === "if-statement") {
|
|
1495
|
+
const stmt = node;
|
|
1496
|
+
this.collectStaticChildInstancesRecursive(stmt.consequent, result, inLoop, providerCtx);
|
|
1497
|
+
if (stmt.alternate) {
|
|
1498
|
+
this.collectStaticChildInstancesRecursive(stmt.alternate, result, inLoop, providerCtx);
|
|
1499
|
+
}
|
|
1149
1500
|
} else if (node.type === "provider") {
|
|
1150
1501
|
const p = node;
|
|
1151
1502
|
const childCtx = this.extendProviderContext(providerCtx, p);
|
|
@@ -1653,8 +2004,8 @@ ${goFields.join(`
|
|
|
1653
2004
|
}
|
|
1654
2005
|
const lines = [];
|
|
1655
2006
|
lines.push("func() string {");
|
|
1656
|
-
|
|
1657
|
-
lines.push(
|
|
2007
|
+
this.usesFmt = true;
|
|
2008
|
+
lines.push(` switch fmt.Sprint(in.${fieldName}) {`);
|
|
1658
2009
|
for (const [k, v] of caseEntries) {
|
|
1659
2010
|
lines.push(` case ${JSON.stringify(k)}: return ${JSON.stringify(v)}`);
|
|
1660
2011
|
}
|
|
@@ -1672,6 +2023,22 @@ ${goFields.join(`
|
|
|
1672
2023
|
return segments.join(" + ");
|
|
1673
2024
|
}
|
|
1674
2025
|
resolveDynamicPropValue(expr, signals, memos, propsParams) {
|
|
2026
|
+
const cmpMatch = expr.match(/^(\w+)\(\)\s*([!=]==?)\s*(?:'([^']*)'|(-?\d+(?:\.\d+)?))\s*$/);
|
|
2027
|
+
if (cmpMatch) {
|
|
2028
|
+
const [, depName, op, strLit, numLit] = cmpMatch;
|
|
2029
|
+
const signal = signals.find((sg) => sg.getter === depName);
|
|
2030
|
+
const init = signal?.initialValue.trim();
|
|
2031
|
+
const initMatch = init !== undefined ? /^(?:'([^'\\]*)'|(-?\d+(?:\.\d+)?))$/.exec(init) : null;
|
|
2032
|
+
if (initMatch) {
|
|
2033
|
+
const initVal = initMatch[1] ?? initMatch[2];
|
|
2034
|
+
const litVal = strLit ?? numLit;
|
|
2035
|
+
const sameKind = initMatch[1] !== undefined === (strLit !== undefined);
|
|
2036
|
+
if (sameKind) {
|
|
2037
|
+
const equal = initVal === litVal;
|
|
2038
|
+
return String(op.startsWith("!") ? !equal : equal);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
1675
2042
|
const getterMatch = expr.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\(\)$/);
|
|
1676
2043
|
if (getterMatch) {
|
|
1677
2044
|
const getterName = getterMatch[1];
|
|
@@ -1681,7 +2048,7 @@ ${goFields.join(`
|
|
|
1681
2048
|
}
|
|
1682
2049
|
const memo = memos.find((m) => m.name === getterName);
|
|
1683
2050
|
if (memo) {
|
|
1684
|
-
return this.
|
|
2051
|
+
return this.computeMemoInitialValueOrNull(memo, signals, propsParams);
|
|
1685
2052
|
}
|
|
1686
2053
|
}
|
|
1687
2054
|
return null;
|
|
@@ -1822,6 +2189,19 @@ ${goFields.join(`
|
|
|
1822
2189
|
return node.name.text;
|
|
1823
2190
|
}
|
|
1824
2191
|
computeMemoInitialValue(memo, signals, propsParams, propFallbackVars = GoTemplateAdapter.EMPTY_PROP_FALLBACK_VARS, goType) {
|
|
2192
|
+
const resolved = this.computeMemoInitialValueOrNull(memo, signals, propsParams, propFallbackVars);
|
|
2193
|
+
if (resolved !== null)
|
|
2194
|
+
return resolved;
|
|
2195
|
+
if (goType === "bool")
|
|
2196
|
+
return "false";
|
|
2197
|
+
if (goType === "string")
|
|
2198
|
+
return '""';
|
|
2199
|
+
if (goType !== undefined && (goType.startsWith("map[") || goType.startsWith("[]") || goType.startsWith("*") || goType.includes("interface{}") || goType === "any")) {
|
|
2200
|
+
return "nil";
|
|
2201
|
+
}
|
|
2202
|
+
return "0";
|
|
2203
|
+
}
|
|
2204
|
+
computeMemoInitialValueOrNull(memo, signals, propsParams, propFallbackVars = GoTemplateAdapter.EMPTY_PROP_FALLBACK_VARS) {
|
|
1825
2205
|
const computation = memo.computation;
|
|
1826
2206
|
const propRef = (propName) => {
|
|
1827
2207
|
const hoisted = propFallbackVars.get(propName);
|
|
@@ -1832,6 +2212,52 @@ ${goFields.join(`
|
|
|
1832
2212
|
const tmplMemo = this.computeTemplateLiteralMemoInitialValue(computation, propsParams);
|
|
1833
2213
|
if (tmplMemo !== null)
|
|
1834
2214
|
return tmplMemo;
|
|
2215
|
+
const eqMatch = computation.match(/^\(\)\s*=>\s*(\w+)\(\)\s*([!=]==?)\s*'([^']*)'\s*$/);
|
|
2216
|
+
if (eqMatch) {
|
|
2217
|
+
const [, depName, op, lit] = eqMatch;
|
|
2218
|
+
const signal = signals.find((sg) => sg.getter === depName);
|
|
2219
|
+
const initLit = signal ? /^'([^'\\]*)'$/.exec(signal.initialValue.trim()) : null;
|
|
2220
|
+
if (initLit) {
|
|
2221
|
+
const equal = initLit[1] === lit;
|
|
2222
|
+
return String(op.startsWith("!") ? !equal : equal);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
const boolPassthrough = computation.match(/^\(\)\s*=>\s*props\.(\w+)\s*\?\?\s*false\s*$/);
|
|
2226
|
+
if (boolPassthrough) {
|
|
2227
|
+
return propRef(boolPassthrough[1]);
|
|
2228
|
+
}
|
|
2229
|
+
const ternMatch = computation.match(/^\(\)\s*=>\s*(\w+)\(\)\s*\?\s*([\w$]+|'[^']*')\s*:\s*([\w$]+|'[^']*')\s*$/);
|
|
2230
|
+
if (ternMatch) {
|
|
2231
|
+
const [, condName, whenTrue, whenFalse] = ternMatch;
|
|
2232
|
+
const resolveBranch = (b) => {
|
|
2233
|
+
const lit = /^'([^']*)'$/.exec(b);
|
|
2234
|
+
if (lit)
|
|
2235
|
+
return JSON.stringify(lit[1]);
|
|
2236
|
+
const constVal = this.moduleStringConsts.get(b);
|
|
2237
|
+
return constVal !== undefined ? JSON.stringify(constVal) : null;
|
|
2238
|
+
};
|
|
2239
|
+
const t = resolveBranch(whenTrue);
|
|
2240
|
+
const f = resolveBranch(whenFalse);
|
|
2241
|
+
if (t !== null && f !== null) {
|
|
2242
|
+
let condGo = null;
|
|
2243
|
+
const condSignal = signals.find((sg) => sg.getter === condName);
|
|
2244
|
+
if (condSignal) {
|
|
2245
|
+
condGo = this.getSignalInitialValueAsGo(condSignal.initialValue, propsParams, propFallbackVars);
|
|
2246
|
+
} else {
|
|
2247
|
+
const condMemo = (this.currentMemos ?? []).find((m) => m.name === condName);
|
|
2248
|
+
if (condMemo) {
|
|
2249
|
+
condGo = this.computeMemoInitialValueOrNull(condMemo, signals, propsParams, propFallbackVars);
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
if (condGo === "true")
|
|
2253
|
+
return t;
|
|
2254
|
+
if (condGo === "false")
|
|
2255
|
+
return f;
|
|
2256
|
+
if (condGo !== null) {
|
|
2257
|
+
return `func() string { if ${condGo} { return ${t} }; return ${f} }()`;
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
1835
2261
|
const arithmeticMatch = computation.match(/\(\)\s*=>\s*(\w+)\(\)\s*([*+\-/])\s*(\d+)/);
|
|
1836
2262
|
if (arithmeticMatch) {
|
|
1837
2263
|
const [, depName, operator, operand] = arithmeticMatch;
|
|
@@ -1851,8 +2277,8 @@ ${goFields.join(`
|
|
|
1851
2277
|
return `${hoisted.varName} ${operator} ${operand}`;
|
|
1852
2278
|
const fieldName = this.capitalizeFieldName(propName);
|
|
1853
2279
|
if (param.type) {
|
|
1854
|
-
const
|
|
1855
|
-
if (
|
|
2280
|
+
const goType = this.typeInfoToGo(param.type, param.defaultValue);
|
|
2281
|
+
if (goType === "interface{}")
|
|
1856
2282
|
return `in.${fieldName}.(int) ${operator} ${operand}`;
|
|
1857
2283
|
}
|
|
1858
2284
|
return `in.${fieldName} ${operator} ${operand}`;
|
|
@@ -1881,8 +2307,8 @@ ${goFields.join(`
|
|
|
1881
2307
|
if (param) {
|
|
1882
2308
|
const fieldName = this.capitalizeFieldName(varName);
|
|
1883
2309
|
if (param.type) {
|
|
1884
|
-
const
|
|
1885
|
-
if (
|
|
2310
|
+
const goType = this.typeInfoToGo(param.type, param.defaultValue);
|
|
2311
|
+
if (goType === "interface{}")
|
|
1886
2312
|
return `in.${fieldName}.(int) ${operator} ${operand}`;
|
|
1887
2313
|
}
|
|
1888
2314
|
return `in.${fieldName} ${operator} ${operand}`;
|
|
@@ -1896,11 +2322,283 @@ ${goFields.join(`
|
|
|
1896
2322
|
return `in.${this.capitalizeFieldName(varName)}`;
|
|
1897
2323
|
}
|
|
1898
2324
|
}
|
|
1899
|
-
|
|
2325
|
+
const blockReturn = this.resolveBlockBodyMemoModuleConst(computation, signals);
|
|
2326
|
+
if (blockReturn !== null && blockReturn.constValue && blockReturn.constType) {
|
|
2327
|
+
return this.convertInitialValue(blockReturn.constValue, blockReturn.constType, propsParams);
|
|
2328
|
+
}
|
|
2329
|
+
const objMemo = this.computeObjectMemoInitialValue(computation);
|
|
2330
|
+
if (objMemo !== null)
|
|
2331
|
+
return objMemo;
|
|
2332
|
+
return null;
|
|
2333
|
+
}
|
|
2334
|
+
resolveBlockBodyMemoModuleConst(computation, signals) {
|
|
2335
|
+
try {
|
|
2336
|
+
const sf = ts.createSourceFile("__memo.ts", `const __x = (${computation});`, ts.ScriptTarget.Latest, false);
|
|
2337
|
+
const stmt = sf.statements[0];
|
|
2338
|
+
if (!stmt || !ts.isVariableStatement(stmt))
|
|
2339
|
+
return null;
|
|
2340
|
+
let init = stmt.declarationList.declarations[0]?.initializer;
|
|
2341
|
+
while (init && ts.isParenthesizedExpression(init))
|
|
2342
|
+
init = init.expression;
|
|
2343
|
+
if (!init || !ts.isArrowFunction(init))
|
|
2344
|
+
return null;
|
|
2345
|
+
const body = init.body;
|
|
2346
|
+
if (!ts.isBlock(body))
|
|
2347
|
+
return null;
|
|
2348
|
+
const varToSignal = new Map;
|
|
2349
|
+
let guardSignalGetter = null;
|
|
2350
|
+
let returnedConst = null;
|
|
2351
|
+
for (const s of body.statements) {
|
|
2352
|
+
if (ts.isVariableStatement(s)) {
|
|
2353
|
+
for (const decl of s.declarationList.declarations) {
|
|
2354
|
+
if (ts.isIdentifier(decl.name) && decl.initializer && ts.isCallExpression(decl.initializer) && ts.isIdentifier(decl.initializer.expression)) {
|
|
2355
|
+
const callee = decl.initializer.expression.text;
|
|
2356
|
+
if (signals.some((sg) => sg.getter === callee)) {
|
|
2357
|
+
varToSignal.set(decl.name.text, callee);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
if (ts.isIfStatement(s) && ts.isPrefixUnaryExpression(s.expression) && s.expression.operator === ts.SyntaxKind.ExclamationToken && ts.isIdentifier(s.expression.operand)) {
|
|
2363
|
+
const guardVar = s.expression.operand.text;
|
|
2364
|
+
const signalGetter = varToSignal.get(guardVar);
|
|
2365
|
+
if (!signalGetter)
|
|
2366
|
+
continue;
|
|
2367
|
+
const thenBlock = ts.isBlock(s.thenStatement) ? s.thenStatement.statements : [s.thenStatement];
|
|
2368
|
+
for (const rs of thenBlock) {
|
|
2369
|
+
if (ts.isReturnStatement(rs) && rs.expression && ts.isIdentifier(rs.expression)) {
|
|
2370
|
+
guardSignalGetter = signalGetter;
|
|
2371
|
+
returnedConst = rs.expression.text;
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
if (guardSignalGetter && returnedConst)
|
|
2376
|
+
break;
|
|
2377
|
+
}
|
|
2378
|
+
if (!guardSignalGetter || !returnedConst)
|
|
2379
|
+
return null;
|
|
2380
|
+
const guardSignal = signals.find((sg) => sg.getter === guardSignalGetter);
|
|
2381
|
+
if (!guardSignal)
|
|
2382
|
+
return null;
|
|
2383
|
+
const iv = guardSignal.initialValue.trim();
|
|
2384
|
+
if (iv !== "null" && iv !== "''" && iv !== '""' && iv !== "0" && iv !== "false") {
|
|
2385
|
+
return null;
|
|
2386
|
+
}
|
|
2387
|
+
const constant = this.localConstants.find((c) => c.name === returnedConst && c.origin?.scope === "module");
|
|
2388
|
+
if (!constant)
|
|
2389
|
+
return null;
|
|
2390
|
+
return {
|
|
2391
|
+
constName: constant.name,
|
|
2392
|
+
constValue: constant.value,
|
|
2393
|
+
constType: constant.type ?? undefined
|
|
2394
|
+
};
|
|
2395
|
+
} catch {
|
|
2396
|
+
return null;
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
computeObjectMemoInitialValue(computation) {
|
|
2400
|
+
const arrow = this.parseLiteralExpression(computation);
|
|
2401
|
+
if (!arrow || !ts.isArrowFunction(arrow) || !ts.isBlock(arrow.body))
|
|
2402
|
+
return null;
|
|
2403
|
+
const searchParamsVars = new Set;
|
|
2404
|
+
let retObj = null;
|
|
2405
|
+
const statements = arrow.body.statements;
|
|
2406
|
+
for (let i = 0;i < statements.length; i++) {
|
|
2407
|
+
const s = statements[i];
|
|
2408
|
+
if (ts.isVariableStatement(s)) {
|
|
2409
|
+
for (const d of s.declarationList.declarations) {
|
|
2410
|
+
if (ts.isIdentifier(d.name) && d.initializer && ts.isCallExpression(d.initializer) && ts.isIdentifier(d.initializer.expression) && d.initializer.arguments.length === 0 && this.searchParamsLocals.has(d.initializer.expression.text)) {
|
|
2411
|
+
searchParamsVars.add(d.name.text);
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
continue;
|
|
2415
|
+
}
|
|
2416
|
+
if (ts.isReturnStatement(s)) {
|
|
2417
|
+
if (i !== statements.length - 1 || !s.expression)
|
|
2418
|
+
return null;
|
|
2419
|
+
let e = s.expression;
|
|
2420
|
+
while (ts.isParenthesizedExpression(e))
|
|
2421
|
+
e = e.expression;
|
|
2422
|
+
if (!ts.isObjectLiteralExpression(e))
|
|
2423
|
+
return null;
|
|
2424
|
+
retObj = e;
|
|
2425
|
+
continue;
|
|
2426
|
+
}
|
|
2427
|
+
return null;
|
|
2428
|
+
}
|
|
2429
|
+
if (!retObj || retObj.properties.length === 0)
|
|
2430
|
+
return null;
|
|
2431
|
+
const env = { searchParamsVars, params: new Map };
|
|
2432
|
+
const entries = [];
|
|
2433
|
+
for (const prop of retObj.properties) {
|
|
2434
|
+
if (!ts.isPropertyAssignment(prop))
|
|
2435
|
+
return null;
|
|
2436
|
+
const key = ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name) ? prop.name.text : null;
|
|
2437
|
+
if (!key)
|
|
2438
|
+
return null;
|
|
2439
|
+
const go = this.lowerCtorExpr(prop.initializer, env);
|
|
2440
|
+
if (go === null)
|
|
2441
|
+
return null;
|
|
2442
|
+
entries.push(`"${this.capitalizeFieldName(key)}": ${go}`);
|
|
2443
|
+
}
|
|
2444
|
+
return `map[string]interface{}{
|
|
2445
|
+
${entries.join(`,
|
|
2446
|
+
`)},
|
|
2447
|
+
}`;
|
|
2448
|
+
}
|
|
2449
|
+
lowerCtorExpr(node, env) {
|
|
2450
|
+
while (ts.isParenthesizedExpression(node))
|
|
2451
|
+
node = node.expression;
|
|
2452
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
2453
|
+
return JSON.stringify(node.text);
|
|
2454
|
+
}
|
|
2455
|
+
if (ts.isNumericLiteral(node))
|
|
2456
|
+
return node.text;
|
|
2457
|
+
if (ts.isIdentifier(node)) {
|
|
2458
|
+
const sub = env.params.get(node.text);
|
|
2459
|
+
if (sub !== undefined)
|
|
2460
|
+
return sub;
|
|
2461
|
+
const c = this.localConstants.find((lc) => lc.name === node.text);
|
|
2462
|
+
if (c?.value !== undefined) {
|
|
2463
|
+
if (c.isModule) {
|
|
2464
|
+
const lit = this.parseLiteralExpression(c.value);
|
|
2465
|
+
if (lit && (ts.isStringLiteral(lit) || ts.isNoSubstitutionTemplateLiteral(lit))) {
|
|
2466
|
+
return JSON.stringify(lit.text);
|
|
2467
|
+
}
|
|
2468
|
+
return null;
|
|
2469
|
+
}
|
|
2470
|
+
if (env.consts?.has(node.text))
|
|
2471
|
+
return null;
|
|
2472
|
+
const inner = this.parseLiteralExpression(c.value);
|
|
2473
|
+
if (!inner)
|
|
2474
|
+
return null;
|
|
2475
|
+
return this.lowerCtorExpr(inner, {
|
|
2476
|
+
...env,
|
|
2477
|
+
consts: new Set([...env.consts ?? [], node.text])
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
return null;
|
|
2481
|
+
}
|
|
2482
|
+
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === this.propsObjectName) {
|
|
2483
|
+
return `in.${this.capitalizeFieldName(node.name.text)}`;
|
|
2484
|
+
}
|
|
2485
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
|
|
2486
|
+
const method = node.expression.name.text;
|
|
2487
|
+
const recv = node.expression.expression;
|
|
2488
|
+
if (method === "get" && ts.isIdentifier(recv) && env.searchParamsVars.has(recv.text) && node.arguments.length === 1 && ts.isStringLiteral(node.arguments[0])) {
|
|
2489
|
+
return `in.SearchParams.Get(${JSON.stringify(node.arguments[0].text)})`;
|
|
2490
|
+
}
|
|
2491
|
+
if (method === "includes" && node.arguments.length === 1) {
|
|
2492
|
+
const arr = this.lowerCtorStringArray(recv);
|
|
2493
|
+
const elem = this.lowerCtorExpr(node.arguments[0], env);
|
|
2494
|
+
if (arr !== null && elem !== null)
|
|
2495
|
+
return `bf.Includes(${arr}, ${elem})`;
|
|
2496
|
+
return null;
|
|
2497
|
+
}
|
|
2498
|
+
if (method === "replace" && node.arguments.length === 2 && node.arguments[0].kind === ts.SyntaxKind.RegularExpressionLiteral && node.arguments[0].getText() === "/\\/+$/" && (ts.isStringLiteral(node.arguments[1]) || ts.isNoSubstitutionTemplateLiteral(node.arguments[1])) && node.arguments[1].text === "") {
|
|
2499
|
+
const recvGo = this.lowerCtorExpr(recv, env);
|
|
2500
|
+
if (recvGo === null)
|
|
2501
|
+
return null;
|
|
2502
|
+
this.needsStringsImport = true;
|
|
2503
|
+
return `strings.TrimRight(${recvGo}, "/")`;
|
|
2504
|
+
}
|
|
2505
|
+
return null;
|
|
2506
|
+
}
|
|
2507
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
|
|
2508
|
+
const fnConst = this.localConstants.find((lc) => lc.name === node.expression.text && lc.isModule);
|
|
2509
|
+
if (fnConst?.value) {
|
|
2510
|
+
const fn = this.parseLiteralExpression(fnConst.value);
|
|
2511
|
+
if (fn && ts.isArrowFunction(fn) && !ts.isBlock(fn.body) && fn.parameters.length === node.arguments.length) {
|
|
2512
|
+
const params = new Map(env.params);
|
|
2513
|
+
for (let i = 0;i < fn.parameters.length; i++) {
|
|
2514
|
+
const p = fn.parameters[i];
|
|
2515
|
+
if (!ts.isIdentifier(p.name))
|
|
2516
|
+
return null;
|
|
2517
|
+
const argGo = this.lowerCtorExpr(node.arguments[i], env);
|
|
2518
|
+
if (argGo === null)
|
|
2519
|
+
return null;
|
|
2520
|
+
params.set(p.name.text, argGo);
|
|
2521
|
+
}
|
|
2522
|
+
return this.lowerCtorExpr(fn.body, { searchParamsVars: env.searchParamsVars, params });
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
return null;
|
|
2526
|
+
}
|
|
2527
|
+
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
2528
|
+
const left = this.lowerCtorExpr(node.left, env);
|
|
2529
|
+
if (left === null)
|
|
2530
|
+
return null;
|
|
2531
|
+
let r = node.right;
|
|
2532
|
+
while (ts.isParenthesizedExpression(r))
|
|
2533
|
+
r = r.expression;
|
|
2534
|
+
if ((ts.isStringLiteral(r) || ts.isNoSubstitutionTemplateLiteral(r)) && r.text === "") {
|
|
2535
|
+
return left;
|
|
2536
|
+
}
|
|
2537
|
+
const right = this.lowerCtorExpr(node.right, env);
|
|
2538
|
+
if (right === null)
|
|
2539
|
+
return null;
|
|
2540
|
+
return `func() string { v := ${left}; if v != "" { return v }; return ${right} }()`;
|
|
2541
|
+
}
|
|
2542
|
+
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
|
|
2543
|
+
const left = this.lowerCtorExpr(node.left, env);
|
|
2544
|
+
const right = this.lowerCtorExpr(node.right, env);
|
|
2545
|
+
if (left === null || right === null)
|
|
2546
|
+
return null;
|
|
2547
|
+
return `func() string { v := ${left}; if v != "" { return v }; return ${right} }()`;
|
|
2548
|
+
}
|
|
2549
|
+
if (ts.isConditionalExpression(node)) {
|
|
2550
|
+
const cond = this.lowerCtorCond(node.condition, env);
|
|
2551
|
+
const t = this.lowerCtorExpr(node.whenTrue, env);
|
|
2552
|
+
const f = this.lowerCtorExpr(node.whenFalse, env);
|
|
2553
|
+
if (cond !== null && t !== null && f !== null) {
|
|
2554
|
+
return `func() string { if ${cond} { return ${t} }; return ${f} }()`;
|
|
2555
|
+
}
|
|
2556
|
+
return null;
|
|
2557
|
+
}
|
|
2558
|
+
return null;
|
|
2559
|
+
}
|
|
2560
|
+
lowerCtorCond(node, env) {
|
|
2561
|
+
while (ts.isParenthesizedExpression(node))
|
|
2562
|
+
node = node.expression;
|
|
2563
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword)
|
|
2564
|
+
return "true";
|
|
2565
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword)
|
|
1900
2566
|
return "false";
|
|
1901
|
-
if (
|
|
1902
|
-
|
|
1903
|
-
|
|
2567
|
+
if (ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.ExclamationToken) {
|
|
2568
|
+
const inner = this.lowerCtorCond(node.operand, env);
|
|
2569
|
+
return inner === null ? null : `!(${inner})`;
|
|
2570
|
+
}
|
|
2571
|
+
if (ts.isBinaryExpression(node)) {
|
|
2572
|
+
const op = node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : node.operatorToken.kind === ts.SyntaxKind.BarBarToken ? "||" : null;
|
|
2573
|
+
if (op) {
|
|
2574
|
+
const l = this.lowerCtorCond(node.left, env);
|
|
2575
|
+
const r = this.lowerCtorCond(node.right, env);
|
|
2576
|
+
return l !== null && r !== null ? `(${l} ${op} ${r})` : null;
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "includes") {
|
|
2580
|
+
return this.lowerCtorExpr(node, env);
|
|
2581
|
+
}
|
|
2582
|
+
return null;
|
|
2583
|
+
}
|
|
2584
|
+
lowerCtorStringArray(node) {
|
|
2585
|
+
let arr = node;
|
|
2586
|
+
if (ts.isIdentifier(node)) {
|
|
2587
|
+
const c = this.localConstants.find((lc) => lc.name === node.text && lc.isModule);
|
|
2588
|
+
if (!c?.value)
|
|
2589
|
+
return null;
|
|
2590
|
+
arr = this.parseLiteralExpression(c.value);
|
|
2591
|
+
}
|
|
2592
|
+
if (!arr || !ts.isArrayLiteralExpression(arr))
|
|
2593
|
+
return null;
|
|
2594
|
+
const elems = [];
|
|
2595
|
+
for (const el of arr.elements) {
|
|
2596
|
+
if (ts.isStringLiteral(el) || ts.isNoSubstitutionTemplateLiteral(el)) {
|
|
2597
|
+
elems.push(JSON.stringify(el.text));
|
|
2598
|
+
} else
|
|
2599
|
+
return null;
|
|
2600
|
+
}
|
|
2601
|
+
return `[]string{${elems.join(", ")}}`;
|
|
1904
2602
|
}
|
|
1905
2603
|
isTemplateLiteralMemo(computation) {
|
|
1906
2604
|
const sf = ts.createSourceFile("__memo.ts", `const __x = (${computation});`, ts.ScriptTarget.Latest, false);
|
|
@@ -1956,6 +2654,10 @@ ${goFields.join(`
|
|
|
1956
2654
|
if (this.typeInfoToGo(memo.type) === "interface{}" && this.isBooleanMemo(memo, signals, propsParamMap)) {
|
|
1957
2655
|
return "bool";
|
|
1958
2656
|
}
|
|
2657
|
+
const blockReturn = this.resolveBlockBodyMemoModuleConst(memo.computation, signals);
|
|
2658
|
+
if (blockReturn?.constType?.kind === "array") {
|
|
2659
|
+
return this.typeInfoToGo(blockReturn.constType);
|
|
2660
|
+
}
|
|
1959
2661
|
return this.typeInfoToGo(memo.type);
|
|
1960
2662
|
}
|
|
1961
2663
|
isBooleanMemo(memo, signals, propsParamMap) {
|
|
@@ -2235,6 +2937,9 @@ ${goFields.join(`
|
|
|
2235
2937
|
if (element.slotId) {
|
|
2236
2938
|
hydrationAttrs += ` ${this.renderSlotMarker(element.slotId)}`;
|
|
2237
2939
|
}
|
|
2940
|
+
if (element.regionId) {
|
|
2941
|
+
hydrationAttrs += ` ${BF_REGION}="${element.regionId}"`;
|
|
2942
|
+
}
|
|
2238
2943
|
const voidElements = [
|
|
2239
2944
|
"area",
|
|
2240
2945
|
"base",
|
|
@@ -2263,8 +2968,9 @@ ${goFields.join(`
|
|
|
2263
2968
|
}
|
|
2264
2969
|
return "";
|
|
2265
2970
|
}
|
|
2266
|
-
const
|
|
2267
|
-
|
|
2971
|
+
const classify = {};
|
|
2972
|
+
const goExpr = this.convertExpressionToGo(expr.expr, classify);
|
|
2973
|
+
if (this.isTemplateFragment(goExpr, classify.parsed?.kind)) {
|
|
2268
2974
|
if (expr.slotId) {
|
|
2269
2975
|
return `{{bfTextStart "${expr.slotId}"}}${goExpr}{{bfTextEnd}}`;
|
|
2270
2976
|
}
|
|
@@ -2275,6 +2981,9 @@ ${goFields.join(`
|
|
|
2275
2981
|
}
|
|
2276
2982
|
return `{{${goExpr}}}`;
|
|
2277
2983
|
}
|
|
2984
|
+
isTemplateFragment(go, kind) {
|
|
2985
|
+
return go.startsWith("{{") || kind === "template-literal";
|
|
2986
|
+
}
|
|
2278
2987
|
renderClientOnlyConditional(cond) {
|
|
2279
2988
|
if (cond.slotId) {
|
|
2280
2989
|
return `{{bfComment "cond-start:${cond.slotId}"}}{{bfComment "cond-end:${cond.slotId}"}}`;
|
|
@@ -2285,6 +2994,8 @@ ${goFields.join(`
|
|
|
2285
2994
|
return emitParsedExpr(expr, this);
|
|
2286
2995
|
}
|
|
2287
2996
|
identifier(name) {
|
|
2997
|
+
if (name === "undefined" || name === "null")
|
|
2998
|
+
return '""';
|
|
2288
2999
|
for (let i = this.loopBindingStack.length - 1;i >= 0; i--) {
|
|
2289
3000
|
const acc = this.loopBindingStack[i].get(name);
|
|
2290
3001
|
if (acc !== undefined)
|
|
@@ -2293,6 +3004,9 @@ ${goFields.join(`
|
|
|
2293
3004
|
const inlined = this.resolveModuleStringConst(name);
|
|
2294
3005
|
if (inlined !== null)
|
|
2295
3006
|
return inlined;
|
|
3007
|
+
const inlinedNum = this.resolveModuleNumericConst(name);
|
|
3008
|
+
if (inlinedNum !== null)
|
|
3009
|
+
return inlinedNum;
|
|
2296
3010
|
const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
|
|
2297
3011
|
if (currentLoopParam && name === currentLoopParam)
|
|
2298
3012
|
return ".";
|
|
@@ -2300,7 +3014,86 @@ ${goFields.join(`
|
|
|
2300
3014
|
return `$${name}`;
|
|
2301
3015
|
if (this.loopVarRefCount.has(name))
|
|
2302
3016
|
return `$${name}`;
|
|
2303
|
-
|
|
3017
|
+
if (this.localConstants.some((c) => c.name === name && !c.isModule && !c.containsArrow)) {
|
|
3018
|
+
this.referencedDerivedConsts.add(name);
|
|
3019
|
+
}
|
|
3020
|
+
return this.searchParamsFieldRef(name) ?? this.rootFieldRef(name);
|
|
3021
|
+
}
|
|
3022
|
+
computeDerivedConstFields(takenFieldNames) {
|
|
3023
|
+
const fields = [];
|
|
3024
|
+
for (const name of this.referencedDerivedConsts) {
|
|
3025
|
+
const fieldName = this.capitalizeFieldName(name);
|
|
3026
|
+
if (takenFieldNames.has(fieldName))
|
|
3027
|
+
continue;
|
|
3028
|
+
const c = this.localConstants.find((lc) => lc.name === name && !lc.isModule && lc.value);
|
|
3029
|
+
if (!c?.value)
|
|
3030
|
+
continue;
|
|
3031
|
+
const expr = this.parseLiteralExpression(c.value);
|
|
3032
|
+
if (!expr)
|
|
3033
|
+
continue;
|
|
3034
|
+
if (!this.isStringExpr(expr, new Set))
|
|
3035
|
+
continue;
|
|
3036
|
+
const init = this.lowerCtorExpr(expr, {
|
|
3037
|
+
searchParamsVars: new Set,
|
|
3038
|
+
params: new Map,
|
|
3039
|
+
consts: new Set([name])
|
|
3040
|
+
});
|
|
3041
|
+
if (init === null)
|
|
3042
|
+
continue;
|
|
3043
|
+
fields.push({ name: fieldName, init });
|
|
3044
|
+
}
|
|
3045
|
+
return fields;
|
|
3046
|
+
}
|
|
3047
|
+
isStringExpr(node, seen) {
|
|
3048
|
+
while (ts.isParenthesizedExpression(node))
|
|
3049
|
+
node = node.expression;
|
|
3050
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isTemplateExpression(node)) {
|
|
3051
|
+
return true;
|
|
3052
|
+
}
|
|
3053
|
+
if (ts.isBinaryExpression(node)) {
|
|
3054
|
+
const op = node.operatorToken.kind;
|
|
3055
|
+
if (op === ts.SyntaxKind.PlusToken) {
|
|
3056
|
+
return this.isStringExpr(node.left, seen) || this.isStringExpr(node.right, seen);
|
|
3057
|
+
}
|
|
3058
|
+
if (op === ts.SyntaxKind.BarBarToken || op === ts.SyntaxKind.QuestionQuestionToken) {
|
|
3059
|
+
return this.isStringExpr(node.left, seen) && this.isStringExpr(node.right, seen);
|
|
3060
|
+
}
|
|
3061
|
+
return false;
|
|
3062
|
+
}
|
|
3063
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
|
|
3064
|
+
const m = node.expression.name.text;
|
|
3065
|
+
const STRING_METHODS = new Set([
|
|
3066
|
+
"replace",
|
|
3067
|
+
"trim",
|
|
3068
|
+
"trimStart",
|
|
3069
|
+
"trimEnd",
|
|
3070
|
+
"toLowerCase",
|
|
3071
|
+
"toUpperCase",
|
|
3072
|
+
"slice",
|
|
3073
|
+
"substring",
|
|
3074
|
+
"substr",
|
|
3075
|
+
"padStart",
|
|
3076
|
+
"padEnd",
|
|
3077
|
+
"concat",
|
|
3078
|
+
"repeat",
|
|
3079
|
+
"get"
|
|
3080
|
+
]);
|
|
3081
|
+
return STRING_METHODS.has(m);
|
|
3082
|
+
}
|
|
3083
|
+
if (ts.isConditionalExpression(node)) {
|
|
3084
|
+
return this.isStringExpr(node.whenTrue, seen) && this.isStringExpr(node.whenFalse, seen);
|
|
3085
|
+
}
|
|
3086
|
+
if (ts.isIdentifier(node)) {
|
|
3087
|
+
if (seen.has(node.text))
|
|
3088
|
+
return false;
|
|
3089
|
+
const c = this.localConstants.find((lc) => lc.name === node.text && !lc.isModule && lc.value);
|
|
3090
|
+
if (c?.value) {
|
|
3091
|
+
const inner = this.parseLiteralExpression(c.value);
|
|
3092
|
+
if (inner)
|
|
3093
|
+
return this.isStringExpr(inner, new Set([...seen, node.text]));
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
return false;
|
|
2304
3097
|
}
|
|
2305
3098
|
isOuterLoopParam(name) {
|
|
2306
3099
|
const top = this.loopParamStack.length - 1;
|
|
@@ -2314,70 +3107,26 @@ ${goFields.join(`
|
|
|
2314
3107
|
const prefix = this.loopParamStack.length > 0 ? "$." : ".";
|
|
2315
3108
|
return `${prefix}${this.capitalizeFieldName(name)}`;
|
|
2316
3109
|
}
|
|
3110
|
+
searchParamsFieldRef(name) {
|
|
3111
|
+
return this.searchParamsLocals.has(name) ? this.rootFieldRef("searchParams") : null;
|
|
3112
|
+
}
|
|
2317
3113
|
collectModuleStringConsts(constants) {
|
|
2318
|
-
|
|
2319
|
-
for (const c of constants ?? []) {
|
|
2320
|
-
if (!c.isModule)
|
|
2321
|
-
continue;
|
|
2322
|
-
if (c.value === undefined)
|
|
2323
|
-
continue;
|
|
2324
|
-
const literal = this.parsePureStringLiteral(c.value);
|
|
2325
|
-
if (literal !== null)
|
|
2326
|
-
map.set(c.name, literal);
|
|
2327
|
-
}
|
|
2328
|
-
return map;
|
|
3114
|
+
return collectModuleStringConstsShared(constants);
|
|
2329
3115
|
}
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
const stmt = sf.statements[0];
|
|
2333
|
-
if (!stmt || !ts.isVariableStatement(stmt))
|
|
2334
|
-
return null;
|
|
2335
|
-
const decl = stmt.declarationList.declarations[0];
|
|
2336
|
-
let init = decl?.initializer;
|
|
2337
|
-
while (init && ts.isParenthesizedExpression(init))
|
|
2338
|
-
init = init.expression;
|
|
2339
|
-
if (!init)
|
|
3116
|
+
resolveModuleStringConst(name) {
|
|
3117
|
+
if (this.loopParamStack.length > 0 && this.loopParamStack[this.loopParamStack.length - 1] === name) {
|
|
2340
3118
|
return null;
|
|
2341
|
-
if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
|
|
2342
|
-
return init.text;
|
|
2343
3119
|
}
|
|
2344
|
-
|
|
2345
|
-
if (joined !== null)
|
|
2346
|
-
return joined;
|
|
2347
|
-
return null;
|
|
2348
|
-
}
|
|
2349
|
-
evalStringArrayJoin(node) {
|
|
2350
|
-
if (!ts.isCallExpression(node))
|
|
2351
|
-
return null;
|
|
2352
|
-
const callee = node.expression;
|
|
2353
|
-
if (!ts.isPropertyAccessExpression(callee))
|
|
3120
|
+
if (this.loopVarRefCount.has(name))
|
|
2354
3121
|
return null;
|
|
2355
|
-
if (
|
|
3122
|
+
if (this.isOuterLoopParam(name))
|
|
2356
3123
|
return null;
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
recv = recv.expression;
|
|
2360
|
-
if (!ts.isArrayLiteralExpression(recv))
|
|
3124
|
+
const value = this.moduleStringConsts.get(name);
|
|
3125
|
+
if (value === undefined)
|
|
2361
3126
|
return null;
|
|
2362
|
-
|
|
2363
|
-
for (const el of recv.elements) {
|
|
2364
|
-
if (ts.isStringLiteral(el) || ts.isNoSubstitutionTemplateLiteral(el)) {
|
|
2365
|
-
parts.push(el.text);
|
|
2366
|
-
} else {
|
|
2367
|
-
return null;
|
|
2368
|
-
}
|
|
2369
|
-
}
|
|
2370
|
-
let sep = ",";
|
|
2371
|
-
if (node.arguments.length >= 1) {
|
|
2372
|
-
const arg = node.arguments[0];
|
|
2373
|
-
if (ts.isStringLiteral(arg) || ts.isNoSubstitutionTemplateLiteral(arg))
|
|
2374
|
-
sep = arg.text;
|
|
2375
|
-
else
|
|
2376
|
-
return null;
|
|
2377
|
-
}
|
|
2378
|
-
return parts.join(sep);
|
|
3127
|
+
return `"${this.escapeGoString(value)}"`;
|
|
2379
3128
|
}
|
|
2380
|
-
|
|
3129
|
+
resolveModuleNumericConst(name) {
|
|
2381
3130
|
if (this.loopParamStack.length > 0 && this.loopParamStack[this.loopParamStack.length - 1] === name) {
|
|
2382
3131
|
return null;
|
|
2383
3132
|
}
|
|
@@ -2385,10 +3134,11 @@ ${goFields.join(`
|
|
|
2385
3134
|
return null;
|
|
2386
3135
|
if (this.isOuterLoopParam(name))
|
|
2387
3136
|
return null;
|
|
2388
|
-
const
|
|
2389
|
-
if (value === undefined)
|
|
3137
|
+
const c = this.localConstants.find((k) => k.name === name && k.isModule && !k.containsArrow);
|
|
3138
|
+
if (!c || c.value === undefined)
|
|
2390
3139
|
return null;
|
|
2391
|
-
|
|
3140
|
+
const v = c.value.trim().replace(/(?<=\d)_(?=\d)/g, "");
|
|
3141
|
+
return /^-?\d+(\.\d+)?$/.test(v) ? v : null;
|
|
2392
3142
|
}
|
|
2393
3143
|
literal(value, literalType) {
|
|
2394
3144
|
if (literalType === "string")
|
|
@@ -2399,7 +3149,7 @@ ${goFields.join(`
|
|
|
2399
3149
|
}
|
|
2400
3150
|
call(callee, args, emit) {
|
|
2401
3151
|
if (callee.kind === "identifier" && args.length === 0) {
|
|
2402
|
-
return this.rootFieldRef(callee.name);
|
|
3152
|
+
return this.searchParamsFieldRef(callee.name) ?? this.rootFieldRef(callee.name);
|
|
2403
3153
|
}
|
|
2404
3154
|
const path = identifierPath(callee);
|
|
2405
3155
|
if (path && this.templatePrimitives[path]) {
|
|
@@ -2430,6 +3180,13 @@ ${goFields.join(`
|
|
|
2430
3180
|
if (result)
|
|
2431
3181
|
return result;
|
|
2432
3182
|
}
|
|
3183
|
+
if (property === "length" && object.kind === "call" && object.callee.kind === "identifier" && object.args.length === 0) {
|
|
3184
|
+
const slice = this.memoBackedLoopSlice.get(object.callee.name);
|
|
3185
|
+
if (slice) {
|
|
3186
|
+
const prefix = this.loopParamStack.length > 0 ? "$." : ".";
|
|
3187
|
+
return `len ${prefix}${slice}`;
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
2433
3190
|
if (object.kind === "higher-order" && (object.method === "find" || object.method === "findLast")) {
|
|
2434
3191
|
const findResult = this.renderHigherOrderExpr(object, emit);
|
|
2435
3192
|
if (findResult) {
|
|
@@ -2442,6 +3199,11 @@ ${goFields.join(`
|
|
|
2442
3199
|
if (object.kind === "identifier" && this.propsObjectName && object.name === this.propsObjectName) {
|
|
2443
3200
|
return this.rootFieldRef(property);
|
|
2444
3201
|
}
|
|
3202
|
+
if (object.kind === "identifier") {
|
|
3203
|
+
const staticValue = this.resolveStaticRecordLiteralIndex(`${object.name}.${property}`);
|
|
3204
|
+
if (staticValue !== null)
|
|
3205
|
+
return staticValue;
|
|
3206
|
+
}
|
|
2445
3207
|
const currentLoopParam = this.loopParamStack[this.loopParamStack.length - 1];
|
|
2446
3208
|
if (object.kind === "identifier" && currentLoopParam && object.name === currentLoopParam) {
|
|
2447
3209
|
return `.${this.capitalizeFieldName(property)}`;
|
|
@@ -2451,34 +3213,43 @@ ${goFields.join(`
|
|
|
2451
3213
|
return `len ${obj}`;
|
|
2452
3214
|
return `${obj}.${this.capitalizeFieldName(property)}`;
|
|
2453
3215
|
}
|
|
3216
|
+
indexAccess(object, index, emit) {
|
|
3217
|
+
return `index ${wrapIfMultiToken(emit(object))} ${wrapIfMultiToken(emit(index))}`;
|
|
3218
|
+
}
|
|
2454
3219
|
binary(op, left, right, emit) {
|
|
2455
3220
|
const l = emit(left);
|
|
2456
3221
|
const r = emit(right);
|
|
3222
|
+
const wl = wrapIfMultiToken(l);
|
|
3223
|
+
const wr = wrapIfMultiToken(r);
|
|
2457
3224
|
switch (op) {
|
|
2458
3225
|
case "===":
|
|
2459
|
-
case "==":
|
|
2460
|
-
|
|
3226
|
+
case "==": {
|
|
3227
|
+
const [el, er] = stringTolerantEqOperands(l, r);
|
|
3228
|
+
return `eq ${wrapIfMultiToken(el)} ${wrapIfMultiToken(er)}`;
|
|
3229
|
+
}
|
|
2461
3230
|
case "!==":
|
|
2462
|
-
case "!=":
|
|
2463
|
-
|
|
3231
|
+
case "!=": {
|
|
3232
|
+
const [el, er] = stringTolerantEqOperands(l, r);
|
|
3233
|
+
return `ne ${wrapIfMultiToken(el)} ${wrapIfMultiToken(er)}`;
|
|
3234
|
+
}
|
|
2464
3235
|
case ">":
|
|
2465
|
-
return `gt ${
|
|
3236
|
+
return `gt ${wl} ${wr}`;
|
|
2466
3237
|
case "<":
|
|
2467
|
-
return `lt ${
|
|
3238
|
+
return `lt ${wl} ${wr}`;
|
|
2468
3239
|
case ">=":
|
|
2469
|
-
return `ge ${
|
|
3240
|
+
return `ge ${wl} ${wr}`;
|
|
2470
3241
|
case "<=":
|
|
2471
|
-
return `le ${
|
|
3242
|
+
return `le ${wl} ${wr}`;
|
|
2472
3243
|
case "+":
|
|
2473
|
-
return `bf_add ${
|
|
3244
|
+
return `bf_add ${wl} ${wr}`;
|
|
2474
3245
|
case "-":
|
|
2475
|
-
return `bf_sub ${
|
|
3246
|
+
return `bf_sub ${wl} ${wr}`;
|
|
2476
3247
|
case "*":
|
|
2477
|
-
return `bf_mul ${
|
|
3248
|
+
return `bf_mul ${wl} ${wr}`;
|
|
2478
3249
|
case "/":
|
|
2479
|
-
return `bf_div ${
|
|
3250
|
+
return `bf_div ${wl} ${wr}`;
|
|
2480
3251
|
case "%":
|
|
2481
|
-
return `bf_mod ${
|
|
3252
|
+
return `bf_mod ${wl} ${wr}`;
|
|
2482
3253
|
default:
|
|
2483
3254
|
return `${l} ${op} ${r}`;
|
|
2484
3255
|
}
|
|
@@ -2492,10 +3263,8 @@ ${goFields.join(`
|
|
|
2492
3263
|
return arg;
|
|
2493
3264
|
}
|
|
2494
3265
|
logical(op, left, right, emit) {
|
|
2495
|
-
const
|
|
2496
|
-
const
|
|
2497
|
-
const wrapLeft = this.needsParens(left) ? `(${l})` : l;
|
|
2498
|
-
const wrapRight = this.needsParens(right) ? `(${r})` : r;
|
|
3266
|
+
const wrapLeft = wrapIfMultiToken(emit(left));
|
|
3267
|
+
const wrapRight = wrapIfMultiToken(emit(right));
|
|
2499
3268
|
if (op === "&&")
|
|
2500
3269
|
return `and ${wrapLeft} ${wrapRight}`;
|
|
2501
3270
|
return `or ${wrapLeft} ${wrapRight}`;
|
|
@@ -2512,7 +3281,8 @@ ${goFields.join(`
|
|
|
2512
3281
|
if (part.type === "string") {
|
|
2513
3282
|
result += part.value;
|
|
2514
3283
|
} else {
|
|
2515
|
-
|
|
3284
|
+
const e = emit(part.expr);
|
|
3285
|
+
result += this.isTemplateFragment(e, part.expr.kind) ? e : `{{${e}}}`;
|
|
2516
3286
|
}
|
|
2517
3287
|
}
|
|
2518
3288
|
return result;
|
|
@@ -2613,6 +3383,11 @@ ${goFields.join(`
|
|
|
2613
3383
|
const recv = emit(object);
|
|
2614
3384
|
return `bf_trim ${wrapIfMultiToken(recv)}`;
|
|
2615
3385
|
}
|
|
3386
|
+
case "toFixed": {
|
|
3387
|
+
const recv = emit(object);
|
|
3388
|
+
const digits = args.length >= 1 ? emit(args[0]) : "0";
|
|
3389
|
+
return `bf_to_fixed ${wrapIfMultiToken(recv)} ${wrapIfMultiToken(digits)}`;
|
|
3390
|
+
}
|
|
2616
3391
|
case "split": {
|
|
2617
3392
|
const recv = emit(object);
|
|
2618
3393
|
if (args.length === 0) {
|
|
@@ -3098,11 +3873,33 @@ ${goFields.join(`
|
|
|
3098
3873
|
return false;
|
|
3099
3874
|
}
|
|
3100
3875
|
}
|
|
3101
|
-
convertExpressionToGo(jsExpr) {
|
|
3876
|
+
convertExpressionToGo(jsExpr, out) {
|
|
3102
3877
|
const trimmed = jsExpr.trim();
|
|
3103
3878
|
if (trimmed === "null" || trimmed === "undefined") {
|
|
3104
3879
|
return '""';
|
|
3105
3880
|
}
|
|
3881
|
+
const staticIndexed = this.resolveStaticRecordLiteralIndex(trimmed);
|
|
3882
|
+
if (staticIndexed !== null) {
|
|
3883
|
+
return staticIndexed;
|
|
3884
|
+
}
|
|
3885
|
+
if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
|
|
3886
|
+
const litConst = (this.localConstants ?? []).find((c) => c.name === trimmed);
|
|
3887
|
+
if (litConst?.value !== undefined) {
|
|
3888
|
+
const v = litConst.value.trim();
|
|
3889
|
+
if (/^-?\d+(\.\d+)?$/.test(v))
|
|
3890
|
+
return v;
|
|
3891
|
+
const strLit = /^'([^'\\]*)'$/.exec(v) ?? /^"([^"\\]*)"$/.exec(v);
|
|
3892
|
+
if (strLit)
|
|
3893
|
+
return JSON.stringify(strLit[1]);
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
const urlBuilt = this.lowerUrlBuilderHelperCall(trimmed);
|
|
3897
|
+
if (urlBuilt !== null)
|
|
3898
|
+
return urlBuilt;
|
|
3899
|
+
const inlined = this.inlineLocalHelperCall(trimmed);
|
|
3900
|
+
if (inlined !== null) {
|
|
3901
|
+
return this.convertExpressionToGo(inlined, out);
|
|
3902
|
+
}
|
|
3106
3903
|
const parsed = parseExpression(trimmed);
|
|
3107
3904
|
const support = isSupported(parsed);
|
|
3108
3905
|
if (!support.supported) {
|
|
@@ -3117,8 +3914,270 @@ ${goFields.join(`
|
|
|
3117
3914
|
});
|
|
3118
3915
|
return `""`;
|
|
3119
3916
|
}
|
|
3917
|
+
if (out)
|
|
3918
|
+
out.parsed = parsed;
|
|
3120
3919
|
return this.renderParsedExpr(parsed);
|
|
3121
3920
|
}
|
|
3921
|
+
inlineLocalHelperCall(jsExpr) {
|
|
3922
|
+
if (this.localHelperNames.size === 0)
|
|
3923
|
+
return null;
|
|
3924
|
+
const head = /^\s*([A-Za-z_$][\w$]*)\s*\(/.exec(jsExpr);
|
|
3925
|
+
if (!head || !this.localHelperNames.has(head[1]))
|
|
3926
|
+
return null;
|
|
3927
|
+
const call = this.parseLiteralExpression(jsExpr);
|
|
3928
|
+
if (!call || !ts.isCallExpression(call) || !ts.isIdentifier(call.expression))
|
|
3929
|
+
return null;
|
|
3930
|
+
if (call.arguments.some((a) => ts.isSpreadElement(a)))
|
|
3931
|
+
return null;
|
|
3932
|
+
const fnConst = this.localConstants.find((c) => c.name === call.expression.text && !c.isModule && c.value);
|
|
3933
|
+
if (!fnConst?.value)
|
|
3934
|
+
return null;
|
|
3935
|
+
const fn = this.parseLiteralExpression(fnConst.value);
|
|
3936
|
+
if (!fn || !ts.isArrowFunction(fn) || ts.isBlock(fn.body))
|
|
3937
|
+
return null;
|
|
3938
|
+
if (fn.parameters.length !== call.arguments.length)
|
|
3939
|
+
return null;
|
|
3940
|
+
if (this.bodyCallsLocalHelper(fn.body))
|
|
3941
|
+
return null;
|
|
3942
|
+
const subs = new Map;
|
|
3943
|
+
for (let i = 0;i < fn.parameters.length; i++) {
|
|
3944
|
+
const p = fn.parameters[i];
|
|
3945
|
+
if (!ts.isIdentifier(p.name))
|
|
3946
|
+
return null;
|
|
3947
|
+
subs.set(p.name.text, `(${call.arguments[i].getText()})`);
|
|
3948
|
+
}
|
|
3949
|
+
if (!this.isSpliceSafeHelperBody(fn.body, new Set(subs.keys())))
|
|
3950
|
+
return null;
|
|
3951
|
+
return this.substituteHelperParams(fn.body, subs);
|
|
3952
|
+
}
|
|
3953
|
+
isSpliceSafeHelperBody(body, paramNames) {
|
|
3954
|
+
let safe = true;
|
|
3955
|
+
const visit = (n) => {
|
|
3956
|
+
if (!safe)
|
|
3957
|
+
return;
|
|
3958
|
+
if (ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n)) {
|
|
3959
|
+
safe = false;
|
|
3960
|
+
return;
|
|
3961
|
+
}
|
|
3962
|
+
if (ts.isShorthandPropertyAssignment(n) && paramNames.has(n.name.text)) {
|
|
3963
|
+
safe = false;
|
|
3964
|
+
return;
|
|
3965
|
+
}
|
|
3966
|
+
ts.forEachChild(n, visit);
|
|
3967
|
+
};
|
|
3968
|
+
visit(body);
|
|
3969
|
+
return safe;
|
|
3970
|
+
}
|
|
3971
|
+
bodyCallsLocalHelper(body) {
|
|
3972
|
+
let found = false;
|
|
3973
|
+
const visit = (n) => {
|
|
3974
|
+
if (found)
|
|
3975
|
+
return;
|
|
3976
|
+
if (ts.isCallExpression(n) && ts.isIdentifier(n.expression)) {
|
|
3977
|
+
const name = n.expression.text;
|
|
3978
|
+
if (this.localConstants.some((c) => c.name === name && !c.isModule && c.containsArrow)) {
|
|
3979
|
+
found = true;
|
|
3980
|
+
return;
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
ts.forEachChild(n, visit);
|
|
3984
|
+
};
|
|
3985
|
+
visit(body);
|
|
3986
|
+
return found;
|
|
3987
|
+
}
|
|
3988
|
+
substituteHelperParams(body, subs) {
|
|
3989
|
+
const sf = body.getSourceFile();
|
|
3990
|
+
const base = body.getStart(sf);
|
|
3991
|
+
const repls = [];
|
|
3992
|
+
const visit = (node) => {
|
|
3993
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
3994
|
+
visit(node.expression);
|
|
3995
|
+
return;
|
|
3996
|
+
}
|
|
3997
|
+
if (ts.isPropertyAssignment(node)) {
|
|
3998
|
+
if (ts.isComputedPropertyName(node.name))
|
|
3999
|
+
visit(node.name);
|
|
4000
|
+
visit(node.initializer);
|
|
4001
|
+
return;
|
|
4002
|
+
}
|
|
4003
|
+
if (ts.isIdentifier(node)) {
|
|
4004
|
+
const sub = subs.get(node.text);
|
|
4005
|
+
if (sub !== undefined) {
|
|
4006
|
+
repls.push({ start: node.getStart(sf) - base, end: node.getEnd() - base, text: sub });
|
|
4007
|
+
return;
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
ts.forEachChild(node, visit);
|
|
4011
|
+
};
|
|
4012
|
+
visit(body);
|
|
4013
|
+
let text = body.getText(sf);
|
|
4014
|
+
for (const r of repls.sort((a, b) => b.start - a.start)) {
|
|
4015
|
+
text = text.slice(0, r.start) + r.text + text.slice(r.end);
|
|
4016
|
+
}
|
|
4017
|
+
return text;
|
|
4018
|
+
}
|
|
4019
|
+
lowerUrlBuilderHelperCall(jsExpr) {
|
|
4020
|
+
if (this.localHelperNames.size === 0)
|
|
4021
|
+
return null;
|
|
4022
|
+
const head = /^\s*([A-Za-z_$][\w$]*)\s*\(/.exec(jsExpr);
|
|
4023
|
+
if (!head || !this.localHelperNames.has(head[1]))
|
|
4024
|
+
return null;
|
|
4025
|
+
const call = this.parseLiteralExpression(jsExpr);
|
|
4026
|
+
if (!call || !ts.isCallExpression(call) || !ts.isIdentifier(call.expression))
|
|
4027
|
+
return null;
|
|
4028
|
+
if (call.arguments.some((a) => ts.isSpreadElement(a)))
|
|
4029
|
+
return null;
|
|
4030
|
+
const fnConst = this.localConstants.find((c) => c.name === call.expression.text && !c.isModule && c.value);
|
|
4031
|
+
if (!fnConst?.value)
|
|
4032
|
+
return null;
|
|
4033
|
+
const fn = this.parseLiteralExpression(fnConst.value);
|
|
4034
|
+
if (!fn || !ts.isArrowFunction(fn) || fn.parameters.length !== call.arguments.length)
|
|
4035
|
+
return null;
|
|
4036
|
+
const subs = new Map;
|
|
4037
|
+
for (let i = 0;i < fn.parameters.length; i++) {
|
|
4038
|
+
const p = fn.parameters[i];
|
|
4039
|
+
if (!ts.isIdentifier(p.name))
|
|
4040
|
+
return null;
|
|
4041
|
+
subs.set(p.name.text, `(${call.arguments[i].getText()})`);
|
|
4042
|
+
}
|
|
4043
|
+
const shape = this.extractUrlBuilder(fn);
|
|
4044
|
+
if (shape)
|
|
4045
|
+
return this.emitUrlBuilder(shape, subs);
|
|
4046
|
+
if (!ts.isBlock(fn.body)) {
|
|
4047
|
+
let body = fn.body;
|
|
4048
|
+
while (ts.isParenthesizedExpression(body))
|
|
4049
|
+
body = body.expression;
|
|
4050
|
+
if (ts.isCallExpression(body) && ts.isIdentifier(body.expression) && this.localHelperNames.has(body.expression.text)) {
|
|
4051
|
+
return this.lowerUrlBuilderHelperCall(this.substituteHelperParams(body, subs));
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4054
|
+
return null;
|
|
4055
|
+
}
|
|
4056
|
+
extractUrlBuilder(arrow) {
|
|
4057
|
+
if (!ts.isBlock(arrow.body))
|
|
4058
|
+
return null;
|
|
4059
|
+
let builderVar = null;
|
|
4060
|
+
let base = null;
|
|
4061
|
+
const sets = [];
|
|
4062
|
+
for (const s of arrow.body.statements) {
|
|
4063
|
+
if (ts.isVariableStatement(s)) {
|
|
4064
|
+
for (const d of s.declarationList.declarations) {
|
|
4065
|
+
if (ts.isIdentifier(d.name) && d.initializer && ts.isNewExpression(d.initializer) && ts.isIdentifier(d.initializer.expression) && d.initializer.expression.text === "URLSearchParams" && (d.initializer.arguments?.length ?? 0) === 0) {
|
|
4066
|
+
builderVar = d.name.text;
|
|
4067
|
+
}
|
|
4068
|
+
}
|
|
4069
|
+
continue;
|
|
4070
|
+
}
|
|
4071
|
+
if (ts.isIfStatement(s) && !s.elseStatement && builderVar) {
|
|
4072
|
+
const set = this.matchUrlSet(s.thenStatement, builderVar);
|
|
4073
|
+
if (!set)
|
|
4074
|
+
return null;
|
|
4075
|
+
sets.push({ guard: s.expression, key: set.key, value: set.value });
|
|
4076
|
+
continue;
|
|
4077
|
+
}
|
|
4078
|
+
if (ts.isExpressionStatement(s) && builderVar) {
|
|
4079
|
+
const set = this.matchUrlSet(s, builderVar);
|
|
4080
|
+
if (set) {
|
|
4081
|
+
sets.push({ guard: null, key: set.key, value: set.value });
|
|
4082
|
+
continue;
|
|
4083
|
+
}
|
|
4084
|
+
return null;
|
|
4085
|
+
}
|
|
4086
|
+
if (ts.isReturnStatement(s) && s.expression) {
|
|
4087
|
+
let e = s.expression;
|
|
4088
|
+
while (ts.isParenthesizedExpression(e))
|
|
4089
|
+
e = e.expression;
|
|
4090
|
+
if (!ts.isConditionalExpression(e))
|
|
4091
|
+
return null;
|
|
4092
|
+
base = e.whenFalse;
|
|
4093
|
+
continue;
|
|
4094
|
+
}
|
|
4095
|
+
return null;
|
|
4096
|
+
}
|
|
4097
|
+
if (!builderVar || !base || sets.length === 0)
|
|
4098
|
+
return null;
|
|
4099
|
+
return { base, sets };
|
|
4100
|
+
}
|
|
4101
|
+
matchUrlSet(node, builderVar) {
|
|
4102
|
+
const stmt = ts.isBlock(node) ? node.statements.length === 1 ? node.statements[0] : null : node;
|
|
4103
|
+
if (!stmt || !ts.isExpressionStatement(stmt))
|
|
4104
|
+
return null;
|
|
4105
|
+
const call = stmt.expression;
|
|
4106
|
+
if (ts.isCallExpression(call) && ts.isPropertyAccessExpression(call.expression) && ts.isIdentifier(call.expression.expression) && call.expression.expression.text === builderVar && call.expression.name.text === "set" && call.arguments.length === 2 && ts.isStringLiteral(call.arguments[0])) {
|
|
4107
|
+
return { key: call.arguments[0].text, value: call.arguments[1] };
|
|
4108
|
+
}
|
|
4109
|
+
return null;
|
|
4110
|
+
}
|
|
4111
|
+
emitUrlBuilder(shape, subs) {
|
|
4112
|
+
const lowerExpr = (n) => this.convertExpressionToGo(this.substituteHelperParams(n, subs));
|
|
4113
|
+
const baseGo = wrapIfMultiToken(lowerExpr(shape.base));
|
|
4114
|
+
const parts = [baseGo];
|
|
4115
|
+
for (const set of shape.sets) {
|
|
4116
|
+
const guardGo = set.guard ? this.lowerUrlGuard(set.guard, subs) : "true";
|
|
4117
|
+
parts.push(`(${guardGo})`);
|
|
4118
|
+
parts.push(JSON.stringify(set.key));
|
|
4119
|
+
parts.push(wrapIfMultiToken(lowerExpr(set.value)));
|
|
4120
|
+
}
|
|
4121
|
+
return `bf_query ${parts.join(" ")}`;
|
|
4122
|
+
}
|
|
4123
|
+
lowerUrlGuard(guard, subs) {
|
|
4124
|
+
let g = guard;
|
|
4125
|
+
while (ts.isParenthesizedExpression(g))
|
|
4126
|
+
g = g.expression;
|
|
4127
|
+
const isBoolShape = ts.isBinaryExpression(g) && [
|
|
4128
|
+
ts.SyntaxKind.EqualsEqualsToken,
|
|
4129
|
+
ts.SyntaxKind.EqualsEqualsEqualsToken,
|
|
4130
|
+
ts.SyntaxKind.ExclamationEqualsToken,
|
|
4131
|
+
ts.SyntaxKind.ExclamationEqualsEqualsToken,
|
|
4132
|
+
ts.SyntaxKind.LessThanToken,
|
|
4133
|
+
ts.SyntaxKind.GreaterThanToken,
|
|
4134
|
+
ts.SyntaxKind.LessThanEqualsToken,
|
|
4135
|
+
ts.SyntaxKind.GreaterThanEqualsToken
|
|
4136
|
+
].includes(g.operatorToken.kind) || ts.isPrefixUnaryExpression(g) && g.operator === ts.SyntaxKind.ExclamationToken || g.kind === ts.SyntaxKind.TrueKeyword || g.kind === ts.SyntaxKind.FalseKeyword;
|
|
4137
|
+
if (isBoolShape) {
|
|
4138
|
+
return this.convertConditionToGo(this.substituteHelperParams(guard, subs)).condition;
|
|
4139
|
+
}
|
|
4140
|
+
const valueGo = wrapIfMultiToken(this.convertExpressionToGo(this.substituteHelperParams(guard, subs)));
|
|
4141
|
+
return `ne ${valueGo} ""`;
|
|
4142
|
+
}
|
|
4143
|
+
resolveStaticRecordLiteralIndex(jsExpr) {
|
|
4144
|
+
const m = /^([A-Za-z_$][\w$]*)\[\s*(?:'([^']*)'|"([^"]*)")\s*\]$/.exec(jsExpr) ?? /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/.exec(jsExpr);
|
|
4145
|
+
if (!m)
|
|
4146
|
+
return null;
|
|
4147
|
+
const key = m[2] ?? m[3];
|
|
4148
|
+
const constInfo = (this.localConstants ?? []).find((c) => c.name === m[1] && c.isModule);
|
|
4149
|
+
if (constInfo?.value === undefined)
|
|
4150
|
+
return null;
|
|
4151
|
+
const sf = ts.createSourceFile("__rec.ts", `(${constInfo.value})`, ts.ScriptTarget.Latest, true);
|
|
4152
|
+
if (sf.statements.length !== 1)
|
|
4153
|
+
return null;
|
|
4154
|
+
const stmt = sf.statements[0];
|
|
4155
|
+
if (!ts.isExpressionStatement(stmt))
|
|
4156
|
+
return null;
|
|
4157
|
+
let parsed = stmt.expression;
|
|
4158
|
+
while (ts.isParenthesizedExpression(parsed))
|
|
4159
|
+
parsed = parsed.expression;
|
|
4160
|
+
if (!ts.isObjectLiteralExpression(parsed))
|
|
4161
|
+
return null;
|
|
4162
|
+
for (const prop of parsed.properties) {
|
|
4163
|
+
if (!ts.isPropertyAssignment(prop))
|
|
4164
|
+
continue;
|
|
4165
|
+
const name = prop.name;
|
|
4166
|
+
const propKey = ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNoSubstitutionTemplateLiteral(name) ? name.text : null;
|
|
4167
|
+
if (propKey !== key)
|
|
4168
|
+
continue;
|
|
4169
|
+
let v = prop.initializer;
|
|
4170
|
+
while (ts.isParenthesizedExpression(v))
|
|
4171
|
+
v = v.expression;
|
|
4172
|
+
if (ts.isNumericLiteral(v))
|
|
4173
|
+
return v.text;
|
|
4174
|
+
if (ts.isStringLiteral(v) || ts.isNoSubstitutionTemplateLiteral(v)) {
|
|
4175
|
+
return JSON.stringify(v.text);
|
|
4176
|
+
}
|
|
4177
|
+
return null;
|
|
4178
|
+
}
|
|
4179
|
+
return null;
|
|
4180
|
+
}
|
|
3122
4181
|
makeLoc() {
|
|
3123
4182
|
return {
|
|
3124
4183
|
file: this.componentName + ".tsx",
|
|
@@ -3292,6 +4351,14 @@ ${goFields.join(`
|
|
|
3292
4351
|
}
|
|
3293
4352
|
return { preamble: obj.preamble, expr: `${obj.expr}.${this.capitalizeFieldName(expr.property)}` };
|
|
3294
4353
|
}
|
|
4354
|
+
case "index-access": {
|
|
4355
|
+
const obj = this.renderConditionExpr(expr.object);
|
|
4356
|
+
const idx = this.renderConditionExpr(expr.index);
|
|
4357
|
+
return {
|
|
4358
|
+
preamble: obj.preamble + idx.preamble,
|
|
4359
|
+
expr: `index ${wrapIfMultiToken(obj.expr)} ${wrapIfMultiToken(idx.expr)}`
|
|
4360
|
+
};
|
|
4361
|
+
}
|
|
3295
4362
|
case "binary": {
|
|
3296
4363
|
const leftNeedsParens = this.needsParensInGoTemplate(expr.left);
|
|
3297
4364
|
const leftResult = this.renderConditionExpr(expr.left);
|
|
@@ -3303,13 +4370,17 @@ ${goFields.join(`
|
|
|
3303
4370
|
let result;
|
|
3304
4371
|
switch (expr.op) {
|
|
3305
4372
|
case "===":
|
|
3306
|
-
case "==":
|
|
3307
|
-
|
|
4373
|
+
case "==": {
|
|
4374
|
+
const [el, er] = stringTolerantEqOperands(left, right);
|
|
4375
|
+
result = `eq ${el} ${er}`;
|
|
3308
4376
|
break;
|
|
4377
|
+
}
|
|
3309
4378
|
case "!==":
|
|
3310
|
-
case "!=":
|
|
3311
|
-
|
|
4379
|
+
case "!=": {
|
|
4380
|
+
const [el, er] = stringTolerantEqOperands(left, right);
|
|
4381
|
+
result = `ne ${el} ${er}`;
|
|
3312
4382
|
break;
|
|
4383
|
+
}
|
|
3313
4384
|
case ">":
|
|
3314
4385
|
result = `gt ${left} ${right}`;
|
|
3315
4386
|
break;
|
|
@@ -3500,6 +4571,45 @@ ${goFields.join(`
|
|
|
3500
4571
|
}
|
|
3501
4572
|
return null;
|
|
3502
4573
|
}
|
|
4574
|
+
queueDynamicChildrenDefine(comp) {
|
|
4575
|
+
const effectiveChildren = comp.children.length > 0 ? comp.children : this.jsxChildrenPropNodes(comp.props);
|
|
4576
|
+
if (effectiveChildren.length === 0)
|
|
4577
|
+
return null;
|
|
4578
|
+
if (this.extractTextChildren(effectiveChildren) !== null)
|
|
4579
|
+
return null;
|
|
4580
|
+
if (this.extractHtmlChildren(effectiveChildren) !== null)
|
|
4581
|
+
return null;
|
|
4582
|
+
if (this.extractScopedHtmlChildren(effectiveChildren) !== null)
|
|
4583
|
+
return null;
|
|
4584
|
+
const name = `${this.componentName}__children_${comp.slotId}`;
|
|
4585
|
+
if (!this.pendingChildrenDefines.some((d) => d.name === name)) {
|
|
4586
|
+
this.pendingChildrenDefines.push({
|
|
4587
|
+
name,
|
|
4588
|
+
content: this.renderChildren(effectiveChildren)
|
|
4589
|
+
});
|
|
4590
|
+
}
|
|
4591
|
+
return name;
|
|
4592
|
+
}
|
|
4593
|
+
queueLoopBodyChildrenDefine(comp) {
|
|
4594
|
+
const effectiveChildren = comp.children.length > 0 ? comp.children : this.jsxChildrenPropNodes(comp.props);
|
|
4595
|
+
if (effectiveChildren.length === 0)
|
|
4596
|
+
return null;
|
|
4597
|
+
if (this.extractTextChildren(effectiveChildren) !== null)
|
|
4598
|
+
return null;
|
|
4599
|
+
if (this.extractHtmlChildren(effectiveChildren) !== null)
|
|
4600
|
+
return null;
|
|
4601
|
+
if (this.extractScopedHtmlChildren(effectiveChildren) !== null)
|
|
4602
|
+
return null;
|
|
4603
|
+
const name = `${this.componentName}__loop_children_${comp.slotId}`;
|
|
4604
|
+
if (!this.pendingChildrenDefines.some((d) => d.name === name)) {
|
|
4605
|
+
const wasInLoop = this.inLoop;
|
|
4606
|
+
this.inLoop = false;
|
|
4607
|
+
const content = this.renderChildren(effectiveChildren);
|
|
4608
|
+
this.inLoop = wasInLoop;
|
|
4609
|
+
this.pendingChildrenDefines.push({ name, content });
|
|
4610
|
+
}
|
|
4611
|
+
return name;
|
|
4612
|
+
}
|
|
3503
4613
|
renderComponent(comp, ctx) {
|
|
3504
4614
|
if (comp.name === "Portal") {
|
|
3505
4615
|
return this.renderPortalComponent(comp);
|
|
@@ -3509,10 +4619,16 @@ ${goFields.join(`
|
|
|
3509
4619
|
}
|
|
3510
4620
|
let templateCall;
|
|
3511
4621
|
if (this.inLoop) {
|
|
3512
|
-
|
|
4622
|
+
const loopBodyDefine = this.queueLoopBodyChildrenDefine(comp);
|
|
4623
|
+
if (loopBodyDefine) {
|
|
4624
|
+
templateCall = `{{template "${comp.name}" (bf_with_children . (bf_tmpl "${loopBodyDefine}" .))}}`;
|
|
4625
|
+
} else {
|
|
4626
|
+
templateCall = `{{template "${comp.name}" .}}`;
|
|
4627
|
+
}
|
|
3513
4628
|
} else if (comp.slotId) {
|
|
3514
4629
|
const suffix = slotIdToFieldSuffix(comp.slotId);
|
|
3515
|
-
|
|
4630
|
+
const childrenDefine = this.queueDynamicChildrenDefine(comp);
|
|
4631
|
+
templateCall = childrenDefine ? `{{template "${comp.name}" (bf_with_children .${comp.name}${suffix} (bf_tmpl "${childrenDefine}" .))}}` : `{{template "${comp.name}" .${comp.name}${suffix}}}`;
|
|
3516
4632
|
} else {
|
|
3517
4633
|
templateCall = `{{template "${comp.name}" .${comp.name}}}`;
|
|
3518
4634
|
}
|
|
@@ -3559,10 +4675,22 @@ ${children}`;
|
|
|
3559
4675
|
}
|
|
3560
4676
|
if (isBooleanAttr(name) || value.presenceOrUndefined) {
|
|
3561
4677
|
const { condition: goCond, preamble } = this.convertConditionToGo(value.expr);
|
|
3562
|
-
|
|
4678
|
+
const body = name.startsWith("aria-") ? `${name}="true"` : name;
|
|
4679
|
+
return `${preamble}{{if ${goCond}}}${body}{{end}}`;
|
|
3563
4680
|
}
|
|
3564
4681
|
const parsed = parseExpression(value.expr.trim());
|
|
3565
|
-
if (parsed.kind === "conditional"
|
|
4682
|
+
if (parsed.kind === "conditional") {
|
|
4683
|
+
const undef = (e) => e.kind === "identifier" && (e.name === "undefined" || e.name === "null") || e.kind === "literal" && (e.value === null || e.value === undefined);
|
|
4684
|
+
const test = parsed.test;
|
|
4685
|
+
if (undef(parsed.alternate) && !undef(parsed.consequent)) {
|
|
4686
|
+
const { condition: goCond, preamble } = this.convertConditionToGo(this.isTemplateFragment(this.renderParsedExpr(test), test.kind) ? value.expr : value.expr.slice(0, value.expr.indexOf("?")).trim());
|
|
4687
|
+
const valueExpr = this.renderParsedExpr(parsed.consequent);
|
|
4688
|
+
const body = `${name}="{{${valueExpr}}}"`;
|
|
4689
|
+
return `${preamble}{{if ${goCond}}}${body}{{end}}`;
|
|
4690
|
+
}
|
|
4691
|
+
return `${name}="${this.renderParsedExpr(parsed)}"`;
|
|
4692
|
+
}
|
|
4693
|
+
if (parsed.kind === "template-literal") {
|
|
3566
4694
|
return `${name}="${this.renderParsedExpr(parsed)}"`;
|
|
3567
4695
|
}
|
|
3568
4696
|
const bareId = value.expr.trim();
|
|
@@ -3571,7 +4699,9 @@ ${children}`;
|
|
|
3571
4699
|
const field = `.${this.capitalizeFieldName(propName)}`;
|
|
3572
4700
|
return `{{if ne ${field} nil}}${name}="{{${this.convertExpressionToGo(value.expr)}}}"{{end}}`;
|
|
3573
4701
|
}
|
|
3574
|
-
|
|
4702
|
+
const exprOut = {};
|
|
4703
|
+
const go = this.convertExpressionToGo(value.expr, exprOut);
|
|
4704
|
+
return this.isTemplateFragment(go, exprOut.parsed?.kind) ? `${name}="${go}"` : `${name}="{{${go}}}"`;
|
|
3575
4705
|
},
|
|
3576
4706
|
emitBooleanAttr: (_value, name) => name,
|
|
3577
4707
|
emitSpread: (value) => {
|
|
@@ -3645,7 +4775,21 @@ ${children}`;
|
|
|
3645
4775
|
}
|
|
3646
4776
|
const inner = s.slice(open + 2, close).trim();
|
|
3647
4777
|
if (inner) {
|
|
3648
|
-
|
|
4778
|
+
const cls = {};
|
|
4779
|
+
const goExpr = this.convertExpressionToGo(inner, cls);
|
|
4780
|
+
const parsed = cls.parsed;
|
|
4781
|
+
if (parsed?.kind === "template-literal") {
|
|
4782
|
+
for (const part of parsed.parts) {
|
|
4783
|
+
if (part.type === "string") {
|
|
4784
|
+
out += this.escapeAttrText(part.value);
|
|
4785
|
+
} else {
|
|
4786
|
+
const e = this.renderParsedExpr(part.expr);
|
|
4787
|
+
out += this.isTemplateFragment(e, part.expr.kind) ? e : `{{${e}}}`;
|
|
4788
|
+
}
|
|
4789
|
+
}
|
|
4790
|
+
} else {
|
|
4791
|
+
out += this.isTemplateFragment(goExpr, parsed?.kind) ? goExpr : `{{${goExpr}}}`;
|
|
4792
|
+
}
|
|
3649
4793
|
} else {
|
|
3650
4794
|
out += s.slice(open, close + 1);
|
|
3651
4795
|
}
|
|
@@ -3665,7 +4809,8 @@ ${children}`;
|
|
|
3665
4809
|
const { condition: goCond, preamble } = this.convertConditionToGo(part.condition);
|
|
3666
4810
|
output += `${preamble}{{if ${goCond}}}${part.whenTrue}{{else}}${part.whenFalse}{{end}}`;
|
|
3667
4811
|
} else if (part.type === "lookup") {
|
|
3668
|
-
const
|
|
4812
|
+
const rawKeyExpr = this.convertExpressionToGo(part.key);
|
|
4813
|
+
const keyExpr = /\s/.test(rawKeyExpr) ? `(${rawKeyExpr})` : rawKeyExpr;
|
|
3669
4814
|
const caseEntries = Object.entries(part.cases);
|
|
3670
4815
|
if (caseEntries.length === 0)
|
|
3671
4816
|
continue;
|