@barefootjs/go-template 0.18.7 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,8 @@ class CompileState {
706
706
  hoistedMemoLocals = new Map;
707
707
  loweringMatchers = [];
708
708
  nillablePropNames = new Set;
709
+ nullishConsumedPropNames = new Set;
710
+ omittableAttrConsumedPropNames = new Set;
709
711
  stringValueNames = new Set;
710
712
  rootScopeNodes = new Set;
711
713
  memoBackedLoopSlice = new Map;
@@ -1455,7 +1457,7 @@ function convertInitialValue(ctx, value, typeInfo, propsParams, preParsed) {
1455
1457
  return `in.${capitalizeFieldName(value)}`;
1456
1458
  }
1457
1459
  }
1458
- const propName = ctx.extractPropNameFromInitialValue(value);
1460
+ const propName = ctx.extractPropNameFromInitialValue(value, preParsed);
1459
1461
  if (propName && propsParams?.some((p) => p.name === propName)) {
1460
1462
  return `in.${capitalizeFieldName(propName)}`;
1461
1463
  }
@@ -2599,11 +2601,12 @@ function recordIndexAccessToGoMap(ctx, val, ir) {
2599
2601
  }
2600
2602
 
2601
2603
  // src/adapter/props/prop-types.ts
2604
+ import { isBooleanAttr } from "@barefootjs/jsx";
2602
2605
  function buildPropTypeOverrides(ctx, ir) {
2603
2606
  const overrides = new Map;
2604
2607
  for (const signal of ir.metadata.signals) {
2605
2608
  const propNames = [signal.initialValue];
2606
- const extracted = ctx.extractPropNameFromInitialValue(signal.initialValue);
2609
+ const extracted = ctx.extractPropNameFromInitialValue(signal.initialValue, signal.parsed);
2607
2610
  if (extracted)
2608
2611
  propNames.push(extracted);
2609
2612
  for (const propName of propNames) {
@@ -2660,11 +2663,96 @@ function collectToFixedPropNames(root) {
2660
2663
  walk(root);
2661
2664
  return names;
2662
2665
  }
2666
+ var NULLISH_SCALAR_GO_TYPES = new Set(["string", "int", "float64", "bool"]);
2667
+ function collectNullishConsumedPropNames(ctx, ir) {
2668
+ const names = new Set;
2669
+ const optionalParams = new Set(ir.metadata.propsParams.filter((p) => p.optional && p.defaultValue == null).map((p) => p.name));
2670
+ if (optionalParams.size === 0)
2671
+ return names;
2672
+ const propsObject = ctx.state.propsObjectName;
2673
+ const propNameOfLeft = (left) => {
2674
+ if (left.kind === "identifier")
2675
+ return left.name;
2676
+ if (left.kind === "member" && !left.computed && left.object.kind === "identifier" && left.object.name === propsObject) {
2677
+ return left.property;
2678
+ }
2679
+ return null;
2680
+ };
2681
+ const isZeroEquivalentLiteral = (right) => right.kind === "literal" && (right.value === "" || right.value === false || right.value === null || right.literalType === "number" && Number(right.value) === 0);
2682
+ const walk = (node) => {
2683
+ if (!node || typeof node !== "object")
2684
+ return;
2685
+ if (Array.isArray(node)) {
2686
+ for (const item of node)
2687
+ walk(item);
2688
+ return;
2689
+ }
2690
+ const rec = node;
2691
+ if (rec.kind === "logical" && rec.op === "??" && rec.left && rec.right) {
2692
+ const propName = propNameOfLeft(rec.left);
2693
+ if (propName && optionalParams.has(propName) && !isZeroEquivalentLiteral(rec.right)) {
2694
+ names.add(propName);
2695
+ }
2696
+ }
2697
+ for (const value of Object.values(rec))
2698
+ walk(value);
2699
+ };
2700
+ walk(ir.root);
2701
+ for (const signal of ir.metadata.signals) {
2702
+ const match = ctx.extractPropFallback(signal.initialValue, signal.parsed);
2703
+ if (!match || !optionalParams.has(match.propName))
2704
+ continue;
2705
+ const f = match.goFallback;
2706
+ if (f === '""' || f === "false" || f === "nil" || Number(f) === 0)
2707
+ continue;
2708
+ names.add(match.propName);
2709
+ }
2710
+ return names;
2711
+ }
2712
+ function collectOmittableAttrConsumedPropNames(ctx, ir) {
2713
+ const names = new Set;
2714
+ const optionalParams = new Set(ir.metadata.propsParams.filter((p) => p.optional && p.defaultValue == null).map((p) => p.name));
2715
+ if (optionalParams.size === 0)
2716
+ return names;
2717
+ const propsObject = ctx.state.propsObjectName;
2718
+ const walk = (node) => {
2719
+ if (!node || typeof node !== "object")
2720
+ return;
2721
+ if (Array.isArray(node)) {
2722
+ for (const item of node)
2723
+ walk(item);
2724
+ return;
2725
+ }
2726
+ const rec = node;
2727
+ if (rec.type === "element" && Array.isArray(rec.attrs)) {
2728
+ for (const attr of rec.attrs) {
2729
+ if (attr.name === "class" || attr.name === "className" || attr.name === "style")
2730
+ continue;
2731
+ if (isBooleanAttr(attr.name))
2732
+ continue;
2733
+ if (attr.value?.kind !== "expression" || attr.value.presenceOrUndefined)
2734
+ continue;
2735
+ const bareId = String(attr.value.expr ?? "").trim();
2736
+ const propName = propsObject && bareId.startsWith(`${propsObject}.`) ? bareId.slice(propsObject.length + 1) : bareId;
2737
+ if (/^[A-Za-z_$][\w$]*$/.test(propName) && optionalParams.has(propName)) {
2738
+ names.add(propName);
2739
+ }
2740
+ }
2741
+ }
2742
+ for (const value of Object.values(rec))
2743
+ walk(value);
2744
+ };
2745
+ walk(ir.root);
2746
+ return names;
2747
+ }
2663
2748
  function resolvePropGoType(ctx, param, propTypeOverrides) {
2664
2749
  const base = propTypeOverrides.get(param.name) ?? typeInfoToGo(ctx, param.type, param.defaultValue);
2665
2750
  if (param.optional && ctx.state.localStructFields.has(base)) {
2666
2751
  return "map[string]interface{}";
2667
2752
  }
2753
+ if (param.optional && param.type.kind === "primitive" && (ctx.state.nullishConsumedPropNames.has(param.name) || ctx.state.omittableAttrConsumedPropNames.has(param.name)) && NULLISH_SCALAR_GO_TYPES.has(base)) {
2754
+ return "interface{}";
2755
+ }
2668
2756
  return base;
2669
2757
  }
2670
2758
  function collectNillablePropNames(ctx, ir) {
@@ -2743,8 +2831,8 @@ class GoTemplateAdapter extends BaseAdapter {
2743
2831
  state: this.state,
2744
2832
  convertExpressionToGo: (jsExpr, out, preParsed) => this.convertExpressionToGo(jsExpr, out, preParsed),
2745
2833
  convertConditionToGo: (jsCondition, preParsed) => this.convertConditionToGo(jsCondition, preParsed),
2746
- extractPropNameFromInitialValue: (initialValue) => this.extractPropNameFromInitialValue(initialValue),
2747
- extractPropFallback: (initialValue) => this.extractPropFallback(initialValue),
2834
+ extractPropNameFromInitialValue: (initialValue, preParsed) => this.extractPropNameFromInitialValue(initialValue, preParsed),
2835
+ extractPropFallback: (initialValue, preParsed) => this.extractPropFallback(initialValue, preParsed),
2748
2836
  resolveModuleStringConst: (name) => this.resolveModuleStringConst(name)
2749
2837
  };
2750
2838
  get errors() {
@@ -2796,6 +2884,10 @@ class GoTemplateAdapter extends BaseAdapter {
2796
2884
  }
2797
2885
  this.state.loweringMatchers = prepareLoweringMatchers(ir.metadata);
2798
2886
  augmentInheritedPropAccesses(ir);
2887
+ this.buildLocalTypeTables(ir, ir.metadata.componentName);
2888
+ this.state.nullishConsumedPropNames = collectNullishConsumedPropNames(this.emitCtx, ir);
2889
+ this.state.omittableAttrConsumedPropNames = collectOmittableAttrConsumedPropNames(this.emitCtx, ir);
2890
+ this.state.nillablePropNames = collectNillablePropNames(this.emitCtx, ir);
2799
2891
  }
2800
2892
  generate(ir, options) {
2801
2893
  this.state.componentName = ir.metadata.componentName;
@@ -2804,7 +2896,6 @@ class GoTemplateAdapter extends BaseAdapter {
2804
2896
  this.state.templateVarCounter = 0;
2805
2897
  this.state.pendingChildrenDefines = [];
2806
2898
  this.primeCompileState(ir);
2807
- this.state.nillablePropNames = collectNillablePropNames(this.emitCtx, ir);
2808
2899
  this.state.stringValueNames = collectStringValueNames(ir);
2809
2900
  if (!options?.siblingTemplatesRegistered) {
2810
2901
  this.checkImportedLoopChildComponents(ir);
@@ -3323,10 +3414,18 @@ ${goFields.join(`
3323
3414
  this.emitStaticBodyWrappers(lines, ir, componentName, staticWithBody, emittedWrapperVars);
3324
3415
  const propFallbackVars = this.collectPropFallbackVars(ir);
3325
3416
  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(` }`);
3417
+ if (info.assertType) {
3418
+ const deref = info.assertType === "int" ? `bf.ToInt(in.${info.fieldName})` : info.assertType === "float64" ? `bf.ToFloat64(in.${info.fieldName})` : `in.${info.fieldName}.(${info.assertType})`;
3419
+ lines.push(` var ${info.varName} ${info.assertType} = ${info.goFallback}`);
3420
+ lines.push(` if in.${info.fieldName} != nil {`);
3421
+ lines.push(` ${info.varName} = ${deref}`);
3422
+ lines.push(` }`);
3423
+ } else {
3424
+ lines.push(` ${info.varName} := in.${info.fieldName}`);
3425
+ lines.push(` if ${info.varName} == ${info.zeroLiteral} {`);
3426
+ lines.push(` ${info.varName} = ${info.goFallback}`);
3427
+ lines.push(` }`);
3428
+ }
3330
3429
  }
3331
3430
  if (propFallbackVars.size > 0)
3332
3431
  lines.push("");
@@ -3406,7 +3505,7 @@ ${goFields.join(`
3406
3505
  const fieldName = capitalizeFieldName(signal.getter);
3407
3506
  if (propFieldNames.has(fieldName))
3408
3507
  continue;
3409
- const fallbackMatch = this.extractPropFallback(signal.initialValue);
3508
+ const fallbackMatch = this.extractPropFallback(signal.initialValue, signal.parsed);
3410
3509
  const hoisted = fallbackMatch ? propFallbackVars.get(fallbackMatch.propName) : undefined;
3411
3510
  if (hoisted) {
3412
3511
  lines.push(` ${fieldName}: ${hoisted.varName},`);
@@ -4252,8 +4351,9 @@ ${goFields.join(`
4252
4351
  for (const nested of findNestedComponents(ir.root)) {
4253
4352
  localTaken.add(`${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`);
4254
4353
  }
4354
+ const propTypeOverrides = buildPropTypeOverrides(this.emitCtx, ir);
4255
4355
  for (const signal of ir.metadata.signals) {
4256
- const match = this.extractPropFallback(signal.initialValue);
4356
+ const match = this.extractPropFallback(signal.initialValue, signal.parsed);
4257
4357
  if (!match)
4258
4358
  continue;
4259
4359
  if (result.has(match.propName))
@@ -4264,6 +4364,8 @@ ${goFields.join(`
4264
4364
  if (goPropDefault(param.defaultValue) !== null)
4265
4365
  continue;
4266
4366
  const fieldName = capitalizeFieldName(match.propName);
4367
+ const concreteType = propTypeOverrides.get(param.name) ?? typeInfoToGo(this.emitCtx, param.type, param.defaultValue);
4368
+ const nullishLowered = NULLISH_SCALAR_GO_TYPES.has(concreteType) && resolvePropGoType(this.emitCtx, param, propTypeOverrides) === "interface{}";
4267
4369
  let zeroLiteral;
4268
4370
  if (match.goFallback === "true" || match.goFallback === "false") {
4269
4371
  zeroLiteral = "false";
@@ -4274,20 +4376,31 @@ ${goFields.join(`
4274
4376
  } else {
4275
4377
  continue;
4276
4378
  }
4277
- if (match.goFallback === zeroLiteral)
4278
- continue;
4279
- if (zeroLiteral === "0" && Number(match.goFallback) === 0)
4280
- continue;
4379
+ if (!nullishLowered) {
4380
+ if (match.goFallback === zeroLiteral)
4381
+ continue;
4382
+ if (zeroLiteral === "0" && Number(match.goFallback) === 0)
4383
+ continue;
4384
+ }
4281
4385
  let varName = match.propName;
4282
4386
  while (localTaken.has(varName) || GO_KEYWORDS.has(varName)) {
4283
4387
  varName += "_";
4284
4388
  }
4285
4389
  localTaken.add(varName);
4286
- result.set(match.propName, { varName, fieldName, goFallback: match.goFallback, zeroLiteral });
4390
+ result.set(match.propName, {
4391
+ varName,
4392
+ fieldName,
4393
+ goFallback: match.goFallback,
4394
+ zeroLiteral,
4395
+ ...nullishLowered ? { assertType: concreteType } : {}
4396
+ });
4287
4397
  }
4288
4398
  return result;
4289
4399
  }
4290
- extractPropFallback(initialValue) {
4400
+ extractPropFallback(initialValue, preParsed) {
4401
+ const structural = preParsed ? this.extractPropFallbackFromParsed(preParsed) : null;
4402
+ if (structural)
4403
+ return structural;
4291
4404
  if (!this.state.propsObjectName)
4292
4405
  return null;
4293
4406
  const trimmed = initialValue.trim();
@@ -4301,9 +4414,38 @@ ${goFields.join(`
4301
4414
  return null;
4302
4415
  return { propName: m[1], goFallback };
4303
4416
  }
4304
- extractPropNameFromInitialValue(initialValue) {
4305
- if (!this.state.propsObjectName)
4417
+ extractPropFallbackFromParsed(preParsed) {
4418
+ if (preParsed.kind !== "logical" || preParsed.op !== "??")
4419
+ return null;
4420
+ const left = preParsed.left;
4421
+ const propName = left.kind === "identifier" && !this.state.propsObjectName ? left.name : left.kind === "member" && !left.computed && left.object.kind === "identifier" && left.object.name === this.state.propsObjectName ? left.property : null;
4422
+ if (!propName)
4423
+ return null;
4424
+ let right = preParsed.right;
4425
+ let negate = "";
4426
+ if (right.kind === "unary" && right.op === "-") {
4427
+ negate = "-";
4428
+ right = right.argument;
4429
+ }
4430
+ if (right.kind !== "literal")
4431
+ return null;
4432
+ if (negate && right.literalType !== "number")
4433
+ return null;
4434
+ if (right.literalType === "string") {
4435
+ return { propName, goFallback: JSON.stringify(right.value) };
4436
+ }
4437
+ const goFallback = goPropDefault(negate + (right.raw ?? String(right.value)));
4438
+ if (goFallback === null)
4306
4439
  return null;
4440
+ return { propName, goFallback };
4441
+ }
4442
+ extractPropNameFromInitialValue(initialValue, preParsed) {
4443
+ if (!this.state.propsObjectName) {
4444
+ if (preParsed?.kind === "logical" && (preParsed.op === "??" || preParsed.op === "||") && preParsed.left.kind === "identifier") {
4445
+ return preParsed.left.name;
4446
+ }
4447
+ return null;
4448
+ }
4307
4449
  const trimmed = initialValue.trim();
4308
4450
  const name = this.state.propsObjectName;
4309
4451
  const direct = new RegExp(`^${name}\\.(\\w+)(?:\\s*(?:\\?\\?|\\|\\|)\\s*.+)?$`);
@@ -4715,8 +4857,20 @@ ${goFields.join(`
4715
4857
  const wrapRight = wrapIfMultiToken(emit(right));
4716
4858
  if (op === "&&")
4717
4859
  return `and ${wrapLeft} ${wrapRight}`;
4860
+ if (op === "??" && this.nillablePropNameOf(left) !== null) {
4861
+ return `bf_nullish ${wrapLeft} ${wrapRight}`;
4862
+ }
4718
4863
  return `or ${wrapLeft} ${wrapRight}`;
4719
4864
  }
4865
+ nillablePropNameOf(expr) {
4866
+ let name = null;
4867
+ if (expr.kind === "identifier") {
4868
+ name = expr.name;
4869
+ } else if (expr.kind === "member" && !expr.computed && expr.object.kind === "identifier" && expr.object.name === this.state.propsObjectName) {
4870
+ name = expr.property;
4871
+ }
4872
+ return name !== null && this.state.nullishConsumedPropNames.has(name) && this.state.nillablePropNames.has(name) ? name : null;
4873
+ }
4720
4874
  conditional(test, consequent, alternate, emit) {
4721
4875
  const t = emit(test);
4722
4876
  const c = this.renderConditionalBranch(consequent);
@@ -6022,7 +6176,7 @@ ${children}`;
6022
6176
  if (css !== null)
6023
6177
  return `style="${css}"`;
6024
6178
  }
6025
- if (isBooleanAttr(name) || value.presenceOrUndefined) {
6179
+ if (isBooleanAttr2(name) || value.presenceOrUndefined) {
6026
6180
  const { condition: goCond, preamble } = this.convertConditionToGo(value.expr, value.parsed);
6027
6181
  const body = name.startsWith("aria-") ? `${name}="true"` : name;
6028
6182
  return `${preamble}{{if ${goCond}}}${body}{{end}}`;
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
24
24
  // src/adapter/go-template-adapter.ts
25
25
  import {
26
26
  BaseAdapter,
27
- isBooleanAttr,
27
+ isBooleanAttr as isBooleanAttr2,
28
28
  parseExpression as parseExpression4,
29
29
  stringifyParsedExpr as stringifyParsedExpr2,
30
30
  parseStyleObjectEntries,
@@ -366,6 +366,8 @@ class CompileState {
366
366
  hoistedMemoLocals = new Map;
367
367
  loweringMatchers = [];
368
368
  nillablePropNames = new Set;
369
+ nullishConsumedPropNames = new Set;
370
+ omittableAttrConsumedPropNames = new Set;
369
371
  stringValueNames = new Set;
370
372
  rootScopeNodes = new Set;
371
373
  memoBackedLoopSlice = new Map;
@@ -1115,7 +1117,7 @@ function convertInitialValue(ctx, value, typeInfo, propsParams, preParsed) {
1115
1117
  return `in.${capitalizeFieldName(value)}`;
1116
1118
  }
1117
1119
  }
1118
- const propName = ctx.extractPropNameFromInitialValue(value);
1120
+ const propName = ctx.extractPropNameFromInitialValue(value, preParsed);
1119
1121
  if (propName && propsParams?.some((p) => p.name === propName)) {
1120
1122
  return `in.${capitalizeFieldName(propName)}`;
1121
1123
  }
@@ -2259,11 +2261,12 @@ function recordIndexAccessToGoMap(ctx, val, ir) {
2259
2261
  }
2260
2262
 
2261
2263
  // src/adapter/props/prop-types.ts
2264
+ import { isBooleanAttr } from "@barefootjs/jsx";
2262
2265
  function buildPropTypeOverrides(ctx, ir) {
2263
2266
  const overrides = new Map;
2264
2267
  for (const signal of ir.metadata.signals) {
2265
2268
  const propNames = [signal.initialValue];
2266
- const extracted = ctx.extractPropNameFromInitialValue(signal.initialValue);
2269
+ const extracted = ctx.extractPropNameFromInitialValue(signal.initialValue, signal.parsed);
2267
2270
  if (extracted)
2268
2271
  propNames.push(extracted);
2269
2272
  for (const propName of propNames) {
@@ -2320,11 +2323,96 @@ function collectToFixedPropNames(root) {
2320
2323
  walk(root);
2321
2324
  return names;
2322
2325
  }
2326
+ var NULLISH_SCALAR_GO_TYPES = new Set(["string", "int", "float64", "bool"]);
2327
+ function collectNullishConsumedPropNames(ctx, ir) {
2328
+ const names = new Set;
2329
+ const optionalParams = new Set(ir.metadata.propsParams.filter((p) => p.optional && p.defaultValue == null).map((p) => p.name));
2330
+ if (optionalParams.size === 0)
2331
+ return names;
2332
+ const propsObject = ctx.state.propsObjectName;
2333
+ const propNameOfLeft = (left) => {
2334
+ if (left.kind === "identifier")
2335
+ return left.name;
2336
+ if (left.kind === "member" && !left.computed && left.object.kind === "identifier" && left.object.name === propsObject) {
2337
+ return left.property;
2338
+ }
2339
+ return null;
2340
+ };
2341
+ const isZeroEquivalentLiteral = (right) => right.kind === "literal" && (right.value === "" || right.value === false || right.value === null || right.literalType === "number" && Number(right.value) === 0);
2342
+ const walk = (node) => {
2343
+ if (!node || typeof node !== "object")
2344
+ return;
2345
+ if (Array.isArray(node)) {
2346
+ for (const item of node)
2347
+ walk(item);
2348
+ return;
2349
+ }
2350
+ const rec = node;
2351
+ if (rec.kind === "logical" && rec.op === "??" && rec.left && rec.right) {
2352
+ const propName = propNameOfLeft(rec.left);
2353
+ if (propName && optionalParams.has(propName) && !isZeroEquivalentLiteral(rec.right)) {
2354
+ names.add(propName);
2355
+ }
2356
+ }
2357
+ for (const value of Object.values(rec))
2358
+ walk(value);
2359
+ };
2360
+ walk(ir.root);
2361
+ for (const signal of ir.metadata.signals) {
2362
+ const match = ctx.extractPropFallback(signal.initialValue, signal.parsed);
2363
+ if (!match || !optionalParams.has(match.propName))
2364
+ continue;
2365
+ const f = match.goFallback;
2366
+ if (f === '""' || f === "false" || f === "nil" || Number(f) === 0)
2367
+ continue;
2368
+ names.add(match.propName);
2369
+ }
2370
+ return names;
2371
+ }
2372
+ function collectOmittableAttrConsumedPropNames(ctx, ir) {
2373
+ const names = new Set;
2374
+ const optionalParams = new Set(ir.metadata.propsParams.filter((p) => p.optional && p.defaultValue == null).map((p) => p.name));
2375
+ if (optionalParams.size === 0)
2376
+ return names;
2377
+ const propsObject = ctx.state.propsObjectName;
2378
+ const walk = (node) => {
2379
+ if (!node || typeof node !== "object")
2380
+ return;
2381
+ if (Array.isArray(node)) {
2382
+ for (const item of node)
2383
+ walk(item);
2384
+ return;
2385
+ }
2386
+ const rec = node;
2387
+ if (rec.type === "element" && Array.isArray(rec.attrs)) {
2388
+ for (const attr of rec.attrs) {
2389
+ if (attr.name === "class" || attr.name === "className" || attr.name === "style")
2390
+ continue;
2391
+ if (isBooleanAttr(attr.name))
2392
+ continue;
2393
+ if (attr.value?.kind !== "expression" || attr.value.presenceOrUndefined)
2394
+ continue;
2395
+ const bareId = String(attr.value.expr ?? "").trim();
2396
+ const propName = propsObject && bareId.startsWith(`${propsObject}.`) ? bareId.slice(propsObject.length + 1) : bareId;
2397
+ if (/^[A-Za-z_$][\w$]*$/.test(propName) && optionalParams.has(propName)) {
2398
+ names.add(propName);
2399
+ }
2400
+ }
2401
+ }
2402
+ for (const value of Object.values(rec))
2403
+ walk(value);
2404
+ };
2405
+ walk(ir.root);
2406
+ return names;
2407
+ }
2323
2408
  function resolvePropGoType(ctx, param, propTypeOverrides) {
2324
2409
  const base = propTypeOverrides.get(param.name) ?? typeInfoToGo(ctx, param.type, param.defaultValue);
2325
2410
  if (param.optional && ctx.state.localStructFields.has(base)) {
2326
2411
  return "map[string]interface{}";
2327
2412
  }
2413
+ if (param.optional && param.type.kind === "primitive" && (ctx.state.nullishConsumedPropNames.has(param.name) || ctx.state.omittableAttrConsumedPropNames.has(param.name)) && NULLISH_SCALAR_GO_TYPES.has(base)) {
2414
+ return "interface{}";
2415
+ }
2328
2416
  return base;
2329
2417
  }
2330
2418
  function collectNillablePropNames(ctx, ir) {
@@ -2403,8 +2491,8 @@ class GoTemplateAdapter extends BaseAdapter {
2403
2491
  state: this.state,
2404
2492
  convertExpressionToGo: (jsExpr, out, preParsed) => this.convertExpressionToGo(jsExpr, out, preParsed),
2405
2493
  convertConditionToGo: (jsCondition, preParsed) => this.convertConditionToGo(jsCondition, preParsed),
2406
- extractPropNameFromInitialValue: (initialValue) => this.extractPropNameFromInitialValue(initialValue),
2407
- extractPropFallback: (initialValue) => this.extractPropFallback(initialValue),
2494
+ extractPropNameFromInitialValue: (initialValue, preParsed) => this.extractPropNameFromInitialValue(initialValue, preParsed),
2495
+ extractPropFallback: (initialValue, preParsed) => this.extractPropFallback(initialValue, preParsed),
2408
2496
  resolveModuleStringConst: (name) => this.resolveModuleStringConst(name)
2409
2497
  };
2410
2498
  get errors() {
@@ -2456,6 +2544,10 @@ class GoTemplateAdapter extends BaseAdapter {
2456
2544
  }
2457
2545
  this.state.loweringMatchers = prepareLoweringMatchers(ir.metadata);
2458
2546
  augmentInheritedPropAccesses(ir);
2547
+ this.buildLocalTypeTables(ir, ir.metadata.componentName);
2548
+ this.state.nullishConsumedPropNames = collectNullishConsumedPropNames(this.emitCtx, ir);
2549
+ this.state.omittableAttrConsumedPropNames = collectOmittableAttrConsumedPropNames(this.emitCtx, ir);
2550
+ this.state.nillablePropNames = collectNillablePropNames(this.emitCtx, ir);
2459
2551
  }
2460
2552
  generate(ir, options) {
2461
2553
  this.state.componentName = ir.metadata.componentName;
@@ -2464,7 +2556,6 @@ class GoTemplateAdapter extends BaseAdapter {
2464
2556
  this.state.templateVarCounter = 0;
2465
2557
  this.state.pendingChildrenDefines = [];
2466
2558
  this.primeCompileState(ir);
2467
- this.state.nillablePropNames = collectNillablePropNames(this.emitCtx, ir);
2468
2559
  this.state.stringValueNames = collectStringValueNames(ir);
2469
2560
  if (!options?.siblingTemplatesRegistered) {
2470
2561
  this.checkImportedLoopChildComponents(ir);
@@ -2983,10 +3074,18 @@ ${goFields.join(`
2983
3074
  this.emitStaticBodyWrappers(lines, ir, componentName, staticWithBody, emittedWrapperVars);
2984
3075
  const propFallbackVars = this.collectPropFallbackVars(ir);
2985
3076
  for (const [, info] of propFallbackVars) {
2986
- lines.push(` ${info.varName} := in.${info.fieldName}`);
2987
- lines.push(` if ${info.varName} == ${info.zeroLiteral} {`);
2988
- lines.push(` ${info.varName} = ${info.goFallback}`);
2989
- lines.push(` }`);
3077
+ if (info.assertType) {
3078
+ const deref = info.assertType === "int" ? `bf.ToInt(in.${info.fieldName})` : info.assertType === "float64" ? `bf.ToFloat64(in.${info.fieldName})` : `in.${info.fieldName}.(${info.assertType})`;
3079
+ lines.push(` var ${info.varName} ${info.assertType} = ${info.goFallback}`);
3080
+ lines.push(` if in.${info.fieldName} != nil {`);
3081
+ lines.push(` ${info.varName} = ${deref}`);
3082
+ lines.push(` }`);
3083
+ } else {
3084
+ lines.push(` ${info.varName} := in.${info.fieldName}`);
3085
+ lines.push(` if ${info.varName} == ${info.zeroLiteral} {`);
3086
+ lines.push(` ${info.varName} = ${info.goFallback}`);
3087
+ lines.push(` }`);
3088
+ }
2990
3089
  }
2991
3090
  if (propFallbackVars.size > 0)
2992
3091
  lines.push("");
@@ -3066,7 +3165,7 @@ ${goFields.join(`
3066
3165
  const fieldName = capitalizeFieldName(signal.getter);
3067
3166
  if (propFieldNames.has(fieldName))
3068
3167
  continue;
3069
- const fallbackMatch = this.extractPropFallback(signal.initialValue);
3168
+ const fallbackMatch = this.extractPropFallback(signal.initialValue, signal.parsed);
3070
3169
  const hoisted = fallbackMatch ? propFallbackVars.get(fallbackMatch.propName) : undefined;
3071
3170
  if (hoisted) {
3072
3171
  lines.push(` ${fieldName}: ${hoisted.varName},`);
@@ -3912,8 +4011,9 @@ ${goFields.join(`
3912
4011
  for (const nested of findNestedComponents(ir.root)) {
3913
4012
  localTaken.add(`${nested.name.charAt(0).toLowerCase()}${nested.name.slice(1)}s`);
3914
4013
  }
4014
+ const propTypeOverrides = buildPropTypeOverrides(this.emitCtx, ir);
3915
4015
  for (const signal of ir.metadata.signals) {
3916
- const match = this.extractPropFallback(signal.initialValue);
4016
+ const match = this.extractPropFallback(signal.initialValue, signal.parsed);
3917
4017
  if (!match)
3918
4018
  continue;
3919
4019
  if (result.has(match.propName))
@@ -3924,6 +4024,8 @@ ${goFields.join(`
3924
4024
  if (goPropDefault(param.defaultValue) !== null)
3925
4025
  continue;
3926
4026
  const fieldName = capitalizeFieldName(match.propName);
4027
+ const concreteType = propTypeOverrides.get(param.name) ?? typeInfoToGo(this.emitCtx, param.type, param.defaultValue);
4028
+ const nullishLowered = NULLISH_SCALAR_GO_TYPES.has(concreteType) && resolvePropGoType(this.emitCtx, param, propTypeOverrides) === "interface{}";
3927
4029
  let zeroLiteral;
3928
4030
  if (match.goFallback === "true" || match.goFallback === "false") {
3929
4031
  zeroLiteral = "false";
@@ -3934,20 +4036,31 @@ ${goFields.join(`
3934
4036
  } else {
3935
4037
  continue;
3936
4038
  }
3937
- if (match.goFallback === zeroLiteral)
3938
- continue;
3939
- if (zeroLiteral === "0" && Number(match.goFallback) === 0)
3940
- continue;
4039
+ if (!nullishLowered) {
4040
+ if (match.goFallback === zeroLiteral)
4041
+ continue;
4042
+ if (zeroLiteral === "0" && Number(match.goFallback) === 0)
4043
+ continue;
4044
+ }
3941
4045
  let varName = match.propName;
3942
4046
  while (localTaken.has(varName) || GO_KEYWORDS.has(varName)) {
3943
4047
  varName += "_";
3944
4048
  }
3945
4049
  localTaken.add(varName);
3946
- result.set(match.propName, { varName, fieldName, goFallback: match.goFallback, zeroLiteral });
4050
+ result.set(match.propName, {
4051
+ varName,
4052
+ fieldName,
4053
+ goFallback: match.goFallback,
4054
+ zeroLiteral,
4055
+ ...nullishLowered ? { assertType: concreteType } : {}
4056
+ });
3947
4057
  }
3948
4058
  return result;
3949
4059
  }
3950
- extractPropFallback(initialValue) {
4060
+ extractPropFallback(initialValue, preParsed) {
4061
+ const structural = preParsed ? this.extractPropFallbackFromParsed(preParsed) : null;
4062
+ if (structural)
4063
+ return structural;
3951
4064
  if (!this.state.propsObjectName)
3952
4065
  return null;
3953
4066
  const trimmed = initialValue.trim();
@@ -3961,9 +4074,38 @@ ${goFields.join(`
3961
4074
  return null;
3962
4075
  return { propName: m[1], goFallback };
3963
4076
  }
3964
- extractPropNameFromInitialValue(initialValue) {
3965
- if (!this.state.propsObjectName)
4077
+ extractPropFallbackFromParsed(preParsed) {
4078
+ if (preParsed.kind !== "logical" || preParsed.op !== "??")
4079
+ return null;
4080
+ const left = preParsed.left;
4081
+ const propName = left.kind === "identifier" && !this.state.propsObjectName ? left.name : left.kind === "member" && !left.computed && left.object.kind === "identifier" && left.object.name === this.state.propsObjectName ? left.property : null;
4082
+ if (!propName)
4083
+ return null;
4084
+ let right = preParsed.right;
4085
+ let negate = "";
4086
+ if (right.kind === "unary" && right.op === "-") {
4087
+ negate = "-";
4088
+ right = right.argument;
4089
+ }
4090
+ if (right.kind !== "literal")
4091
+ return null;
4092
+ if (negate && right.literalType !== "number")
4093
+ return null;
4094
+ if (right.literalType === "string") {
4095
+ return { propName, goFallback: JSON.stringify(right.value) };
4096
+ }
4097
+ const goFallback = goPropDefault(negate + (right.raw ?? String(right.value)));
4098
+ if (goFallback === null)
3966
4099
  return null;
4100
+ return { propName, goFallback };
4101
+ }
4102
+ extractPropNameFromInitialValue(initialValue, preParsed) {
4103
+ if (!this.state.propsObjectName) {
4104
+ if (preParsed?.kind === "logical" && (preParsed.op === "??" || preParsed.op === "||") && preParsed.left.kind === "identifier") {
4105
+ return preParsed.left.name;
4106
+ }
4107
+ return null;
4108
+ }
3967
4109
  const trimmed = initialValue.trim();
3968
4110
  const name = this.state.propsObjectName;
3969
4111
  const direct = new RegExp(`^${name}\\.(\\w+)(?:\\s*(?:\\?\\?|\\|\\|)\\s*.+)?$`);
@@ -4375,8 +4517,20 @@ ${goFields.join(`
4375
4517
  const wrapRight = wrapIfMultiToken(emit(right));
4376
4518
  if (op === "&&")
4377
4519
  return `and ${wrapLeft} ${wrapRight}`;
4520
+ if (op === "??" && this.nillablePropNameOf(left) !== null) {
4521
+ return `bf_nullish ${wrapLeft} ${wrapRight}`;
4522
+ }
4378
4523
  return `or ${wrapLeft} ${wrapRight}`;
4379
4524
  }
4525
+ nillablePropNameOf(expr) {
4526
+ let name = null;
4527
+ if (expr.kind === "identifier") {
4528
+ name = expr.name;
4529
+ } else if (expr.kind === "member" && !expr.computed && expr.object.kind === "identifier" && expr.object.name === this.state.propsObjectName) {
4530
+ name = expr.property;
4531
+ }
4532
+ return name !== null && this.state.nullishConsumedPropNames.has(name) && this.state.nillablePropNames.has(name) ? name : null;
4533
+ }
4380
4534
  conditional(test, consequent, alternate, emit) {
4381
4535
  const t = emit(test);
4382
4536
  const c = this.renderConditionalBranch(consequent);
@@ -5682,7 +5836,7 @@ ${children}`;
5682
5836
  if (css !== null)
5683
5837
  return `style="${css}"`;
5684
5838
  }
5685
- if (isBooleanAttr(name) || value.presenceOrUndefined) {
5839
+ if (isBooleanAttr2(name) || value.presenceOrUndefined) {
5686
5840
  const { condition: goCond, preamble } = this.convertConditionToGo(value.expr, value.parsed);
5687
5841
  const body = name.startsWith("aria-") ? `${name}="true"` : name;
5688
5842
  return `${preamble}{{if ${goCond}}}${body}{{end}}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/go-template",
3
- "version": "0.18.7",
3
+ "version": "0.19.0",
4
4
  "description": "Go html/template adapter for BarefootJS - generates Go template files from IR",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -49,7 +49,7 @@
49
49
  "directory": "packages/adapter-go-template"
50
50
  },
51
51
  "dependencies": {
52
- "@barefootjs/shared": "0.18.7"
52
+ "@barefootjs/shared": "0.19.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "@barefootjs/jsx": ">=0.2.0",
@@ -57,6 +57,6 @@
57
57
  },
58
58
  "devDependencies": {
59
59
  "@barefootjs/adapter-tests": "0.1.0",
60
- "@barefootjs/jsx": "0.18.7"
60
+ "@barefootjs/jsx": "0.19.0"
61
61
  }
62
62
  }