@barefootjs/go-template 0.18.7 → 0.19.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/build.js CHANGED
@@ -364,7 +364,7 @@ var init_path = __esm(() => {
364
364
  // src/adapter/go-template-adapter.ts
365
365
  import {
366
366
  BaseAdapter,
367
- isBooleanAttr,
367
+ isBooleanAttr as isBooleanAttr2,
368
368
  parseExpression as parseExpression4,
369
369
  stringifyParsedExpr as stringifyParsedExpr2,
370
370
  parseStyleObjectEntries,
@@ -706,6 +706,10 @@ class CompileState {
706
706
  hoistedMemoLocals = new Map;
707
707
  loweringMatchers = [];
708
708
  nillablePropNames = new Set;
709
+ nullishConsumedPropNames = new Set;
710
+ omittableAttrConsumedPropNames = new Set;
711
+ textConsumedPropNames = new Set;
712
+ presenceCheckedPropNames = new Set;
709
713
  stringValueNames = new Set;
710
714
  rootScopeNodes = new Set;
711
715
  memoBackedLoopSlice = new Map;
@@ -1449,15 +1453,28 @@ function parsedLiteralToGo(ctx, expr, typeInfo) {
1449
1453
 
1450
1454
  // src/adapter/value/value-lowering.ts
1451
1455
  var EMPTY_PROP_FALLBACK_VARS = new Map;
1456
+ function nillableAwarePropRef(ctx, propName, expectedType) {
1457
+ const fieldRef = `in.${capitalizeFieldName(propName)}`;
1458
+ const scalar = expectedType.kind === "primitive" ? expectedType : expectedType.kind === "union" && expectedType.unionTypes?.length === 2 ? expectedType.unionTypes.find((t) => t.primitive !== "undefined" && t.primitive !== "null") : undefined;
1459
+ if (ctx.state.nillablePropNames.has(propName) && scalar?.kind === "primitive") {
1460
+ const goType = scalar.primitive === "boolean" ? "bool" : scalar.primitive === "number" ? "float64" : scalar.primitive === "string" ? "string" : null;
1461
+ if (goType) {
1462
+ const zero = goType === "bool" ? "false" : goType === "string" ? '""' : "0";
1463
+ return `func() ${goType} { if v, ok := ${fieldRef}.(${goType}); ok { return v }; return ${zero} }()`;
1464
+ }
1465
+ }
1466
+ return fieldRef;
1467
+ }
1452
1468
  function convertInitialValue(ctx, value, typeInfo, propsParams, preParsed) {
1469
+ const propRef = (propName2) => nillableAwarePropRef(ctx, propName2, typeInfo);
1453
1470
  if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {
1454
1471
  if (propsParams?.some((p) => p.name === value)) {
1455
- return `in.${capitalizeFieldName(value)}`;
1472
+ return propRef(value);
1456
1473
  }
1457
1474
  }
1458
- const propName = ctx.extractPropNameFromInitialValue(value);
1475
+ const propName = ctx.extractPropNameFromInitialValue(value, preParsed);
1459
1476
  if (propName && propsParams?.some((p) => p.name === propName)) {
1460
- return `in.${capitalizeFieldName(propName)}`;
1477
+ return propRef(propName);
1461
1478
  }
1462
1479
  if (typeInfo.kind === "primitive") {
1463
1480
  if (typeInfo.primitive === "boolean") {
@@ -1524,19 +1541,20 @@ function objectLiteralToGoMap(ctx, expr) {
1524
1541
  return null;
1525
1542
  return `map[string]interface{}{${entries.join(", ")}}`;
1526
1543
  }
1527
- function getSignalInitialValueAsGo(ctx, initialValue, propsParams, propFallbackVars = EMPTY_PROP_FALLBACK_VARS) {
1544
+ function getSignalInitialValueAsGo(ctx, initialValue, propsParams, propFallbackVars = EMPTY_PROP_FALLBACK_VARS, signalType) {
1545
+ const propRef = (propName2) => signalType ? nillableAwarePropRef(ctx, propName2, signalType) : `in.${capitalizeFieldName(propName2)}`;
1528
1546
  if (propsParams.some((p) => p.name === initialValue)) {
1529
1547
  const hoisted = propFallbackVars.get(initialValue);
1530
1548
  if (hoisted)
1531
1549
  return hoisted.varName;
1532
- return `in.${capitalizeFieldName(initialValue)}`;
1550
+ return propRef(initialValue);
1533
1551
  }
1534
1552
  const propName = ctx.extractPropNameFromInitialValue(initialValue);
1535
1553
  if (propName && propsParams.some((p) => p.name === propName)) {
1536
1554
  const hoisted = propFallbackVars.get(propName);
1537
1555
  if (hoisted)
1538
1556
  return hoisted.varName;
1539
- return `in.${capitalizeFieldName(propName)}`;
1557
+ return propRef(propName);
1540
1558
  }
1541
1559
  if (/^-?\d+$/.test(initialValue)) {
1542
1560
  return initialValue;
@@ -2006,9 +2024,34 @@ var EMPTY_PROP_FALLBACK_VARS2 = new Map;
2006
2024
  function getterCallName(e) {
2007
2025
  return e.kind === "call" && e.callee.kind === "identifier" && e.args.length === 0 ? e.callee.name : null;
2008
2026
  }
2027
+ function isBooleanTypeInfo(t) {
2028
+ if (t.kind === "primitive")
2029
+ return t.primitive === "boolean";
2030
+ if (t.kind === "union" && t.unionTypes?.length === 2) {
2031
+ const scalar = t.unionTypes.find((u) => u.primitive !== "undefined" && u.primitive !== "null");
2032
+ return scalar?.kind === "primitive" && scalar.primitive === "boolean";
2033
+ }
2034
+ return false;
2035
+ }
2036
+ function isBooleanTypedGetter(ctx, name, signals) {
2037
+ const signal = signals.find((s) => s.getter === name);
2038
+ if (signal)
2039
+ return signal.type !== undefined && isBooleanTypeInfo(signal.type);
2040
+ const memo = (ctx.state.currentMemos ?? []).find((m) => m.name === name);
2041
+ return memo?.type !== undefined && isBooleanTypeInfo(memo.type);
2042
+ }
2009
2043
  function propsMemberName(e) {
2010
2044
  return e.kind === "member" && !e.computed && e.object.kind === "identifier" && e.object.name === "props" ? e.property : null;
2011
2045
  }
2046
+ function propNameForPropsBinding(ctx, e) {
2047
+ const propsObject = ctx.state.propsObjectName;
2048
+ if (e.kind === "member" && !e.computed && e.object.kind === "identifier" && e.object.name === propsObject) {
2049
+ return e.property;
2050
+ }
2051
+ if (!propsObject && e.kind === "identifier")
2052
+ return e.name;
2053
+ return null;
2054
+ }
2012
2055
  function matchFilterArmMemo(ctx, body, signals, propsParams) {
2013
2056
  const cb = asCallbackMethodCall2(body);
2014
2057
  if (!cb || cb.method !== "filter")
@@ -2147,6 +2190,17 @@ function memoInitialFromParsedBody(ctx, body, signals, propsParams, propFallback
2147
2190
  if (propName)
2148
2191
  return propRef(propName);
2149
2192
  }
2193
+ if (body.kind === "binary" && (body.op === "!==" || body.op === "!=" || body.op === "===" || body.op === "==")) {
2194
+ const other = body.right.kind === "identifier" && body.right.name === "undefined" ? body.left : body.left.kind === "identifier" && body.left.name === "undefined" ? body.right : null;
2195
+ const propName = other ? propNameForPropsBinding(ctx, other) : null;
2196
+ if (propName) {
2197
+ const param = propsParams.find((p) => p.name === propName);
2198
+ if (param && ctx.state.nillablePropNames.has(propName)) {
2199
+ const isNe = body.op === "!==" || body.op === "!=";
2200
+ return `in.${capitalizeFieldName(propName)} ${isNe ? "!=" : "=="} nil`;
2201
+ }
2202
+ }
2203
+ }
2150
2204
  if (body.kind === "conditional") {
2151
2205
  const condName = getterCallName(body.test);
2152
2206
  if (condName) {
@@ -2181,6 +2235,18 @@ function memoInitialFromParsedBody(ctx, body, signals, propsParams, propFallback
2181
2235
  }
2182
2236
  }
2183
2237
  }
2238
+ if (condName) {
2239
+ const tName = getterCallName(body.consequent);
2240
+ const fName = getterCallName(body.alternate);
2241
+ if (tName && fName && isBooleanTypedGetter(ctx, tName, signals) && isBooleanTypedGetter(ctx, fName, signals)) {
2242
+ const condGo = resolveGetterValueAsGo(ctx, condName, signals, propsParams, propFallbackVars, resolving);
2243
+ const tGo = resolveGetterValueAsGo(ctx, tName, signals, propsParams, propFallbackVars, resolving);
2244
+ const fGo = resolveGetterValueAsGo(ctx, fName, signals, propsParams, propFallbackVars, resolving);
2245
+ if (condGo !== null && tGo !== null && fGo !== null) {
2246
+ return `func() bool { if ${condGo} { return ${tGo} }; return ${fGo} }()`;
2247
+ }
2248
+ }
2249
+ }
2184
2250
  }
2185
2251
  if (body.kind === "binary" && ["*", "+", "-", "/"].includes(body.op) && body.right.kind === "literal" && body.right.literalType === "number" && typeof body.right.value === "number" && Number.isInteger(body.right.value) && body.right.value >= 0) {
2186
2252
  const operator = body.op;
@@ -2269,7 +2335,7 @@ function computeMemoInitialValueOrNull(ctx, memo, signals, propsParams, propFall
2269
2335
  function resolveGetterValueAsGo(ctx, name, signals, propsParams, propFallbackVars, resolving = new Set) {
2270
2336
  const signal = signals.find((s) => s.getter === name);
2271
2337
  if (signal) {
2272
- return getSignalInitialValueAsGo(ctx, signal.initialValue, propsParams, propFallbackVars);
2338
+ return getSignalInitialValueAsGo(ctx, signal.initialValue, propsParams, propFallbackVars, signal.type);
2273
2339
  }
2274
2340
  const memo = (ctx.state.currentMemos ?? []).find((m) => m.name === name);
2275
2341
  if (memo) {
@@ -2599,11 +2665,12 @@ function recordIndexAccessToGoMap(ctx, val, ir) {
2599
2665
  }
2600
2666
 
2601
2667
  // src/adapter/props/prop-types.ts
2668
+ import { isBooleanAttr } from "@barefootjs/jsx";
2602
2669
  function buildPropTypeOverrides(ctx, ir) {
2603
2670
  const overrides = new Map;
2604
2671
  for (const signal of ir.metadata.signals) {
2605
2672
  const propNames = [signal.initialValue];
2606
- const extracted = ctx.extractPropNameFromInitialValue(signal.initialValue);
2673
+ const extracted = ctx.extractPropNameFromInitialValue(signal.initialValue, signal.parsed);
2607
2674
  if (extracted)
2608
2675
  propNames.push(extracted);
2609
2676
  for (const propName of propNames) {
@@ -2660,11 +2727,146 @@ function collectToFixedPropNames(root) {
2660
2727
  walk(root);
2661
2728
  return names;
2662
2729
  }
2730
+ var NULLISH_SCALAR_GO_TYPES = new Set(["string", "int", "float64", "bool"]);
2731
+ function collectNullishConsumedPropNames(ctx, ir) {
2732
+ const names = new Set;
2733
+ const optionalParams = new Set(ir.metadata.propsParams.filter((p) => p.optional && p.defaultValue == null).map((p) => p.name));
2734
+ if (optionalParams.size === 0)
2735
+ return names;
2736
+ const propsObject = ctx.state.propsObjectName;
2737
+ const propNameOfLeft = (left) => {
2738
+ if (left.kind === "identifier")
2739
+ return left.name;
2740
+ if (left.kind === "member" && !left.computed && left.object.kind === "identifier") {
2741
+ if (left.object.name === propsObject)
2742
+ return left.property;
2743
+ return left.object.name;
2744
+ }
2745
+ return null;
2746
+ };
2747
+ const isZeroEquivalentLiteral = (right) => right.kind === "literal" && (right.value === "" || right.value === false || right.value === null || right.literalType === "number" && Number(right.value) === 0);
2748
+ const walk = (node) => {
2749
+ if (!node || typeof node !== "object")
2750
+ return;
2751
+ if (Array.isArray(node)) {
2752
+ for (const item of node)
2753
+ walk(item);
2754
+ return;
2755
+ }
2756
+ const rec = node;
2757
+ if (rec.kind === "logical" && rec.op === "??" && rec.left && rec.right) {
2758
+ const propName = propNameOfLeft(rec.left);
2759
+ if (propName && optionalParams.has(propName) && !isZeroEquivalentLiteral(rec.right)) {
2760
+ names.add(propName);
2761
+ }
2762
+ }
2763
+ for (const value of Object.values(rec))
2764
+ walk(value);
2765
+ };
2766
+ walk(ir.root);
2767
+ for (const signal of ir.metadata.signals) {
2768
+ const match = ctx.extractPropFallback(signal.initialValue, signal.parsed);
2769
+ if (!match || !optionalParams.has(match.propName))
2770
+ continue;
2771
+ const f = match.goFallback;
2772
+ if (f === '""' || f === "false" || f === "nil" || Number(f) === 0)
2773
+ continue;
2774
+ names.add(match.propName);
2775
+ }
2776
+ return names;
2777
+ }
2778
+ function collectOmittableAttrConsumedPropNames(ctx, ir) {
2779
+ const names = new Set;
2780
+ const optionalParams = new Set(ir.metadata.propsParams.filter((p) => p.optional && p.defaultValue == null).map((p) => p.name));
2781
+ if (optionalParams.size === 0)
2782
+ return names;
2783
+ const propsObject = ctx.state.propsObjectName;
2784
+ const walk = (node) => {
2785
+ if (!node || typeof node !== "object")
2786
+ return;
2787
+ if (Array.isArray(node)) {
2788
+ for (const item of node)
2789
+ walk(item);
2790
+ return;
2791
+ }
2792
+ const rec = node;
2793
+ if (rec.type === "element" && Array.isArray(rec.attrs)) {
2794
+ for (const attr of rec.attrs) {
2795
+ if (attr.name === "class" || attr.name === "className" || attr.name === "style")
2796
+ continue;
2797
+ if (isBooleanAttr(attr.name))
2798
+ continue;
2799
+ if (attr.value?.kind !== "expression" || attr.value.presenceOrUndefined)
2800
+ continue;
2801
+ const bareId = String(attr.value.expr ?? "").trim();
2802
+ const propName = propsObject && bareId.startsWith(`${propsObject}.`) ? bareId.slice(propsObject.length + 1) : bareId;
2803
+ if (/^[A-Za-z_$][\w$]*$/.test(propName) && optionalParams.has(propName)) {
2804
+ names.add(propName);
2805
+ }
2806
+ }
2807
+ }
2808
+ for (const value of Object.values(rec))
2809
+ walk(value);
2810
+ };
2811
+ walk(ir.root);
2812
+ return names;
2813
+ }
2814
+ function collectTextConsumedPropNames(ctx, ir) {
2815
+ const names = new Set;
2816
+ const optionalParams = new Set(ir.metadata.propsParams.filter((p) => p.optional && p.defaultValue == null && p.type.kind === "primitive").map((p) => p.name));
2817
+ if (optionalParams.size === 0)
2818
+ return names;
2819
+ const propsObject = ctx.state.propsObjectName;
2820
+ const walk = (node) => {
2821
+ if (!node || typeof node !== "object")
2822
+ return;
2823
+ if (Array.isArray(node)) {
2824
+ for (const item of node)
2825
+ walk(item);
2826
+ return;
2827
+ }
2828
+ const rec = node;
2829
+ if (rec.type === "expression") {
2830
+ const bareId = String(rec.expr ?? "").trim();
2831
+ const propName = propsObject && bareId.startsWith(`${propsObject}.`) ? bareId.slice(propsObject.length + 1) : bareId;
2832
+ if (/^[A-Za-z_$][\w$]*$/.test(propName) && optionalParams.has(propName)) {
2833
+ names.add(propName);
2834
+ }
2835
+ }
2836
+ for (const value of Object.values(rec))
2837
+ walk(value);
2838
+ };
2839
+ walk(ir.root);
2840
+ return names;
2841
+ }
2842
+ function collectPresenceCheckedPropNames(ctx, ir) {
2843
+ const names = new Set;
2844
+ const optionalParams = new Set(ir.metadata.propsParams.filter((p) => p.optional && p.defaultValue == null && p.type.kind === "primitive").map((p) => p.name));
2845
+ if (optionalParams.size === 0)
2846
+ return names;
2847
+ const propsObject = ctx.state.propsObjectName;
2848
+ const isUndefinedCheck = (op) => op === "!==" || op === "!=" || op === "===" || op === "==";
2849
+ for (const memo of ir.metadata.memos) {
2850
+ const body = memo.parsed;
2851
+ if (!body || body.kind !== "binary" || !isUndefinedCheck(body.op))
2852
+ continue;
2853
+ const other = body.right.kind === "identifier" && body.right.name === "undefined" ? body.left : body.left.kind === "identifier" && body.left.name === "undefined" ? body.right : null;
2854
+ if (!other)
2855
+ continue;
2856
+ const propName = other.kind === "member" && !other.computed && other.object.kind === "identifier" && other.object.name === propsObject ? other.property : !propsObject && other.kind === "identifier" ? other.name : null;
2857
+ if (propName && optionalParams.has(propName))
2858
+ names.add(propName);
2859
+ }
2860
+ return names;
2861
+ }
2663
2862
  function resolvePropGoType(ctx, param, propTypeOverrides) {
2664
2863
  const base = propTypeOverrides.get(param.name) ?? typeInfoToGo(ctx, param.type, param.defaultValue);
2665
2864
  if (param.optional && ctx.state.localStructFields.has(base)) {
2666
2865
  return "map[string]interface{}";
2667
2866
  }
2867
+ if (param.optional && param.type.kind === "primitive" && (ctx.state.nullishConsumedPropNames.has(param.name) || ctx.state.omittableAttrConsumedPropNames.has(param.name) || ctx.state.textConsumedPropNames.has(param.name) || ctx.state.presenceCheckedPropNames.has(param.name)) && NULLISH_SCALAR_GO_TYPES.has(base)) {
2868
+ return "interface{}";
2869
+ }
2668
2870
  return base;
2669
2871
  }
2670
2872
  function collectNillablePropNames(ctx, ir) {
@@ -2743,8 +2945,8 @@ class GoTemplateAdapter extends BaseAdapter {
2743
2945
  state: this.state,
2744
2946
  convertExpressionToGo: (jsExpr, out, preParsed) => this.convertExpressionToGo(jsExpr, out, preParsed),
2745
2947
  convertConditionToGo: (jsCondition, preParsed) => this.convertConditionToGo(jsCondition, preParsed),
2746
- extractPropNameFromInitialValue: (initialValue) => this.extractPropNameFromInitialValue(initialValue),
2747
- extractPropFallback: (initialValue) => this.extractPropFallback(initialValue),
2948
+ extractPropNameFromInitialValue: (initialValue, preParsed) => this.extractPropNameFromInitialValue(initialValue, preParsed),
2949
+ extractPropFallback: (initialValue, preParsed) => this.extractPropFallback(initialValue, preParsed),
2748
2950
  resolveModuleStringConst: (name) => this.resolveModuleStringConst(name)
2749
2951
  };
2750
2952
  get errors() {
@@ -2796,6 +2998,12 @@ class GoTemplateAdapter extends BaseAdapter {
2796
2998
  }
2797
2999
  this.state.loweringMatchers = prepareLoweringMatchers(ir.metadata);
2798
3000
  augmentInheritedPropAccesses(ir);
3001
+ this.buildLocalTypeTables(ir, ir.metadata.componentName);
3002
+ this.state.nullishConsumedPropNames = collectNullishConsumedPropNames(this.emitCtx, ir);
3003
+ this.state.omittableAttrConsumedPropNames = collectOmittableAttrConsumedPropNames(this.emitCtx, ir);
3004
+ this.state.textConsumedPropNames = collectTextConsumedPropNames(this.emitCtx, ir);
3005
+ this.state.presenceCheckedPropNames = collectPresenceCheckedPropNames(this.emitCtx, ir);
3006
+ this.state.nillablePropNames = collectNillablePropNames(this.emitCtx, ir);
2799
3007
  }
2800
3008
  generate(ir, options) {
2801
3009
  this.state.componentName = ir.metadata.componentName;
@@ -2804,7 +3012,6 @@ class GoTemplateAdapter extends BaseAdapter {
2804
3012
  this.state.templateVarCounter = 0;
2805
3013
  this.state.pendingChildrenDefines = [];
2806
3014
  this.primeCompileState(ir);
2807
- this.state.nillablePropNames = collectNillablePropNames(this.emitCtx, ir);
2808
3015
  this.state.stringValueNames = collectStringValueNames(ir);
2809
3016
  if (!options?.siblingTemplatesRegistered) {
2810
3017
  this.checkImportedLoopChildComponents(ir);
@@ -3323,10 +3530,18 @@ ${goFields.join(`
3323
3530
  this.emitStaticBodyWrappers(lines, ir, componentName, staticWithBody, emittedWrapperVars);
3324
3531
  const propFallbackVars = this.collectPropFallbackVars(ir);
3325
3532
  for (const [, info] of propFallbackVars) {
3326
- lines.push(` ${info.varName} := in.${info.fieldName}`);
3327
- lines.push(` if ${info.varName} == ${info.zeroLiteral} {`);
3328
- lines.push(` ${info.varName} = ${info.goFallback}`);
3329
- lines.push(` }`);
3533
+ if (info.assertType) {
3534
+ const deref = info.assertType === "int" ? `bf.ToInt(in.${info.fieldName})` : info.assertType === "float64" ? `bf.ToFloat64(in.${info.fieldName})` : `in.${info.fieldName}.(${info.assertType})`;
3535
+ lines.push(` var ${info.varName} ${info.assertType} = ${info.goFallback}`);
3536
+ lines.push(` if in.${info.fieldName} != nil {`);
3537
+ lines.push(` ${info.varName} = ${deref}`);
3538
+ lines.push(` }`);
3539
+ } else {
3540
+ lines.push(` ${info.varName} := in.${info.fieldName}`);
3541
+ lines.push(` if ${info.varName} == ${info.zeroLiteral} {`);
3542
+ lines.push(` ${info.varName} = ${info.goFallback}`);
3543
+ lines.push(` }`);
3544
+ }
3330
3545
  }
3331
3546
  if (propFallbackVars.size > 0)
3332
3547
  lines.push("");
@@ -3406,7 +3621,7 @@ ${goFields.join(`
3406
3621
  const fieldName = capitalizeFieldName(signal.getter);
3407
3622
  if (propFieldNames.has(fieldName))
3408
3623
  continue;
3409
- const fallbackMatch = this.extractPropFallback(signal.initialValue);
3624
+ const fallbackMatch = this.extractPropFallback(signal.initialValue, signal.parsed);
3410
3625
  const hoisted = fallbackMatch ? propFallbackVars.get(fallbackMatch.propName) : undefined;
3411
3626
  if (hoisted) {
3412
3627
  lines.push(` ${fieldName}: ${hoisted.varName},`);
@@ -4252,8 +4467,9 @@ ${goFields.join(`
4252
4467
  for (const nested of findNestedComponents(ir.root)) {
4253
4468
  localTaken.add(`${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`);
4254
4469
  }
4470
+ const propTypeOverrides = buildPropTypeOverrides(this.emitCtx, ir);
4255
4471
  for (const signal of ir.metadata.signals) {
4256
- const match = this.extractPropFallback(signal.initialValue);
4472
+ const match = this.extractPropFallback(signal.initialValue, signal.parsed);
4257
4473
  if (!match)
4258
4474
  continue;
4259
4475
  if (result.has(match.propName))
@@ -4264,6 +4480,8 @@ ${goFields.join(`
4264
4480
  if (goPropDefault(param.defaultValue) !== null)
4265
4481
  continue;
4266
4482
  const fieldName = capitalizeFieldName(match.propName);
4483
+ const concreteType = propTypeOverrides.get(param.name) ?? typeInfoToGo(this.emitCtx, param.type, param.defaultValue);
4484
+ const nullishLowered = NULLISH_SCALAR_GO_TYPES.has(concreteType) && resolvePropGoType(this.emitCtx, param, propTypeOverrides) === "interface{}";
4267
4485
  let zeroLiteral;
4268
4486
  if (match.goFallback === "true" || match.goFallback === "false") {
4269
4487
  zeroLiteral = "false";
@@ -4274,20 +4492,31 @@ ${goFields.join(`
4274
4492
  } else {
4275
4493
  continue;
4276
4494
  }
4277
- if (match.goFallback === zeroLiteral)
4278
- continue;
4279
- if (zeroLiteral === "0" && Number(match.goFallback) === 0)
4280
- continue;
4495
+ if (!nullishLowered) {
4496
+ if (match.goFallback === zeroLiteral)
4497
+ continue;
4498
+ if (zeroLiteral === "0" && Number(match.goFallback) === 0)
4499
+ continue;
4500
+ }
4281
4501
  let varName = match.propName;
4282
4502
  while (localTaken.has(varName) || GO_KEYWORDS.has(varName)) {
4283
4503
  varName += "_";
4284
4504
  }
4285
4505
  localTaken.add(varName);
4286
- result.set(match.propName, { varName, fieldName, goFallback: match.goFallback, zeroLiteral });
4506
+ result.set(match.propName, {
4507
+ varName,
4508
+ fieldName,
4509
+ goFallback: match.goFallback,
4510
+ zeroLiteral,
4511
+ ...nullishLowered ? { assertType: concreteType } : {}
4512
+ });
4287
4513
  }
4288
4514
  return result;
4289
4515
  }
4290
- extractPropFallback(initialValue) {
4516
+ extractPropFallback(initialValue, preParsed) {
4517
+ const structural = preParsed ? this.extractPropFallbackFromParsed(preParsed) : null;
4518
+ if (structural)
4519
+ return structural;
4291
4520
  if (!this.state.propsObjectName)
4292
4521
  return null;
4293
4522
  const trimmed = initialValue.trim();
@@ -4301,9 +4530,38 @@ ${goFields.join(`
4301
4530
  return null;
4302
4531
  return { propName: m[1], goFallback };
4303
4532
  }
4304
- extractPropNameFromInitialValue(initialValue) {
4305
- if (!this.state.propsObjectName)
4533
+ extractPropFallbackFromParsed(preParsed) {
4534
+ if (preParsed.kind !== "logical" || preParsed.op !== "??")
4535
+ return null;
4536
+ const left = preParsed.left;
4537
+ const propName = left.kind === "identifier" && !this.state.propsObjectName ? left.name : left.kind === "member" && !left.computed && left.object.kind === "identifier" && left.object.name === this.state.propsObjectName ? left.property : null;
4538
+ if (!propName)
4539
+ return null;
4540
+ let right = preParsed.right;
4541
+ let negate = "";
4542
+ if (right.kind === "unary" && right.op === "-") {
4543
+ negate = "-";
4544
+ right = right.argument;
4545
+ }
4546
+ if (right.kind !== "literal")
4547
+ return null;
4548
+ if (negate && right.literalType !== "number")
4549
+ return null;
4550
+ if (right.literalType === "string") {
4551
+ return { propName, goFallback: JSON.stringify(right.value) };
4552
+ }
4553
+ const goFallback = goPropDefault(negate + (right.raw ?? String(right.value)));
4554
+ if (goFallback === null)
4555
+ return null;
4556
+ return { propName, goFallback };
4557
+ }
4558
+ extractPropNameFromInitialValue(initialValue, preParsed) {
4559
+ if (!this.state.propsObjectName) {
4560
+ if (preParsed?.kind === "logical" && (preParsed.op === "??" || preParsed.op === "||") && preParsed.left.kind === "identifier") {
4561
+ return preParsed.left.name;
4562
+ }
4306
4563
  return null;
4564
+ }
4307
4565
  const trimmed = initialValue.trim();
4308
4566
  const name = this.state.propsObjectName;
4309
4567
  const direct = new RegExp(`^${name}\\.(\\w+)(?:\\s*(?:\\?\\?|\\|\\|)\\s*.+)?$`);
@@ -4427,10 +4685,22 @@ ${goFields.join(`
4427
4685
  }
4428
4686
  return goExpr;
4429
4687
  }
4688
+ const finalExpr = this.textNillablePropNameOf(classify.parsed) !== null ? `bf_string ${wrapIfMultiToken(goExpr)}` : goExpr;
4430
4689
  if (expr.slotId) {
4431
- return `{{bfTextStart "${expr.slotId}"}}{{${goExpr}}}{{bfTextEnd}}`;
4690
+ return `{{bfTextStart "${expr.slotId}"}}{{${finalExpr}}}{{bfTextEnd}}`;
4432
4691
  }
4433
- return `{{${goExpr}}}`;
4692
+ return `{{${finalExpr}}}`;
4693
+ }
4694
+ textNillablePropNameOf(expr) {
4695
+ if (!expr)
4696
+ return null;
4697
+ let name = null;
4698
+ if (expr.kind === "identifier") {
4699
+ name = expr.name;
4700
+ } else if (expr.kind === "member" && !expr.computed && expr.object.kind === "identifier" && expr.object.name === this.state.propsObjectName) {
4701
+ name = expr.property;
4702
+ }
4703
+ return name !== null && this.state.textConsumedPropNames.has(name) && this.state.nillablePropNames.has(name) ? name : null;
4434
4704
  }
4435
4705
  isTemplateFragment(go, kind) {
4436
4706
  return go.startsWith("{{") || kind === "template-literal";
@@ -4646,7 +4916,7 @@ ${goFields.join(`
4646
4916
  }
4647
4917
  const obj = emit(object);
4648
4918
  if (property === "length")
4649
- return `len ${obj}`;
4919
+ return `bf_length ${wrapIfMultiToken(obj)}`;
4650
4920
  if (optional) {
4651
4921
  return `bf_get ${wrapIfMultiToken(obj)} ${JSON.stringify(goFieldNameForKey(property))}`;
4652
4922
  }
@@ -4715,8 +4985,24 @@ ${goFields.join(`
4715
4985
  const wrapRight = wrapIfMultiToken(emit(right));
4716
4986
  if (op === "&&")
4717
4987
  return `and ${wrapLeft} ${wrapRight}`;
4988
+ if (op === "??" && this.nillablePropNameOf(left) !== null) {
4989
+ return `bf_nullish ${wrapLeft} ${wrapRight}`;
4990
+ }
4718
4991
  return `or ${wrapLeft} ${wrapRight}`;
4719
4992
  }
4993
+ nillablePropNameOf(expr) {
4994
+ let name = null;
4995
+ if (expr.kind === "identifier") {
4996
+ name = expr.name;
4997
+ } else if (expr.kind === "member" && !expr.computed && expr.object.kind === "identifier") {
4998
+ if (expr.object.name === this.state.propsObjectName) {
4999
+ name = expr.property;
5000
+ } else {
5001
+ name = expr.object.name;
5002
+ }
5003
+ }
5004
+ return name !== null && this.state.nullishConsumedPropNames.has(name) && this.state.nillablePropNames.has(name) ? name : null;
5005
+ }
4720
5006
  conditional(test, consequent, alternate, emit) {
4721
5007
  const t = emit(test);
4722
5008
  const c = this.renderConditionalBranch(consequent);
@@ -5148,9 +5434,6 @@ ${goFields.join(`
5148
5434
  renderPredicateCondition(pred, param, datumField) {
5149
5435
  return this.renderFilterExpr(pred, param, new Map, datumField ?? undefined);
5150
5436
  }
5151
- needsParens(expr) {
5152
- return expr.kind === "logical" || expr.kind === "unary" || expr.kind === "conditional";
5153
- }
5154
5437
  splitPreamble(rendered) {
5155
5438
  if (!rendered.includes("{{"))
5156
5439
  return null;
@@ -5563,7 +5846,8 @@ ${goFields.join(`
5563
5846
  return plain(this.rootFieldRef(expr.callee.name));
5564
5847
  }
5565
5848
  if (expr.callee.kind === "identifier" && (identifierPath(expr.callee) ?? expr.callee.name) === "isValidElement" && expr.args.length === 1) {
5566
- return this.renderConditionExpr(expr.args[0]);
5849
+ const inner = this.renderConditionExpr(expr.args[0]);
5850
+ return { preamble: inner.preamble, expr: `(bf_is_element ${wrapIfMultiToken(inner.expr)})` };
5567
5851
  }
5568
5852
  if (expr.callee.kind === "identifier" && !this.templatePrimitives[identifierPath(expr.callee) ?? ""]) {
5569
5853
  const path = identifierPath(expr.callee) ?? expr.callee.name;
@@ -5680,9 +5964,9 @@ ${goFields.join(`
5680
5964
  const leftResult = this.renderConditionExpr(expr.left);
5681
5965
  const rightResult = this.renderConditionExpr(expr.right);
5682
5966
  const preamble = leftResult.preamble + rightResult.preamble;
5683
- const wrapLeft = this.needsParens(expr.left) ? `(${leftResult.expr})` : leftResult.expr;
5684
- const wrapRight = this.needsParens(expr.right) ? `(${rightResult.expr})` : rightResult.expr;
5685
- const result = expr.op === "&&" ? `and ${wrapLeft} ${wrapRight}` : `or ${wrapLeft} ${wrapRight}`;
5967
+ const wrapLeft = wrapIfMultiToken(leftResult.expr);
5968
+ const wrapRight = wrapIfMultiToken(rightResult.expr);
5969
+ const result = expr.op === "&&" ? `and ${wrapLeft} ${wrapRight}` : expr.op === "??" && this.nillablePropNameOf(expr.left) !== null ? `bf_nullish ${wrapLeft} ${wrapRight}` : `or ${wrapLeft} ${wrapRight}`;
5686
5970
  return { preamble, expr: result };
5687
5971
  }
5688
5972
  case "conditional": {
@@ -6022,7 +6306,7 @@ ${children}`;
6022
6306
  if (css !== null)
6023
6307
  return `style="${css}"`;
6024
6308
  }
6025
- if (isBooleanAttr(name) || value.presenceOrUndefined) {
6309
+ if (isBooleanAttr2(name) || value.presenceOrUndefined) {
6026
6310
  const { condition: goCond, preamble } = this.convertConditionToGo(value.expr, value.parsed);
6027
6311
  const body = name.startsWith("aria-") ? `${name}="true"` : name;
6028
6312
  return `${preamble}{{if ${goCond}}}${body}{{end}}`;
@@ -6095,7 +6379,11 @@ ${children}`;
6095
6379
  if (e.kind === "expr" && !isSupported(parseExpression4(e.expr)).supported)
6096
6380
  return null;
6097
6381
  }
6098
- return entries.map((e) => e.kind === "literal" ? `${this.escapeAttrText(e.cssKey)}:${this.escapeAttrText(e.value)}` : `${this.escapeAttrText(e.cssKey)}:{{${this.convertExpressionToGo(e.expr)}}}`).join(";");
6382
+ const args = entries.flatMap((e) => [
6383
+ JSON.stringify(e.cssKey),
6384
+ e.kind === "literal" ? JSON.stringify(e.value) : wrapIfMultiToken(this.convertExpressionToGo(e.expr))
6385
+ ]);
6386
+ return `{{bf_style_object ${args.join(" ")}}}`;
6099
6387
  }
6100
6388
  renderAttributes(element) {
6101
6389
  const parts = [];