@barefootjs/mojolicious 0.7.0 → 0.9.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-present BarefootJS Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -8,7 +8,10 @@ import {
8
8
  identifierPath,
9
9
  emitParsedExpr,
10
10
  emitIRNode,
11
- emitAttrValue
11
+ emitAttrValue,
12
+ augmentInheritedPropAccesses,
13
+ parseRecordIndexAccess,
14
+ evalStringArrayJoin
12
15
  } from "@barefootjs/jsx";
13
16
 
14
17
  // src/adapter/boolean-result.ts
@@ -95,7 +98,10 @@ function parsePureStringLiteral(source) {
95
98
  if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
96
99
  return init.text;
97
100
  }
98
- return null;
101
+ return evalStringArrayJoin(source);
102
+ }
103
+ function perlHashKey(name) {
104
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : `'${name.replace(/'/g, "\\'")}'`;
99
105
  }
100
106
 
101
107
  class MojoAdapter extends BaseAdapter {
@@ -112,7 +118,9 @@ class MojoAdapter extends BaseAdapter {
112
118
  propsParams = [];
113
119
  stringValueNames = new Set;
114
120
  moduleStringConsts = new Map;
121
+ localConstants = [];
115
122
  loopBoundNames = new Map;
123
+ nullableOptionalProps = new Set;
116
124
  constructor(options = {}) {
117
125
  super();
118
126
  this.options = {
@@ -123,7 +131,9 @@ class MojoAdapter extends BaseAdapter {
123
131
  generate(ir, options) {
124
132
  this.componentName = ir.metadata.componentName;
125
133
  this.propsObjectName = ir.metadata.propsObjectName ?? null;
134
+ augmentInheritedPropAccesses(ir);
126
135
  this.propsParams = ir.metadata.propsParams.map((p) => ({ name: p.name }));
136
+ this.nullableOptionalProps = new Set(ir.metadata.propsParams.filter((p) => p.defaultValue === undefined && !p.isRest && p.type?.kind !== "primitive").map((p) => p.name));
127
137
  this.stringValueNames = new Set;
128
138
  for (const s of ir.metadata.signals) {
129
139
  if (isStringTypeInfo(s.type) || isBareStringLiteral(s.initialValue)) {
@@ -135,6 +145,7 @@ class MojoAdapter extends BaseAdapter {
135
145
  this.stringValueNames.add(p.name);
136
146
  }
137
147
  this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
148
+ this.localConstants = ir.metadata.localConstants ?? [];
138
149
  this.loopBoundNames.clear();
139
150
  this.errors = [];
140
151
  this.childrenCaptureCounter = 0;
@@ -497,20 +508,20 @@ ${renderedChildren}` : renderedChildren;
497
508
  `);
498
509
  }
499
510
  componentPropEmitter = {
500
- emitLiteral: (value, name) => `${name} => '${value.value}'`,
511
+ emitLiteral: (value, name) => `${perlHashKey(name)} => '${value.value}'`,
501
512
  emitExpression: (value, name) => {
502
513
  if (value.parts) {
503
- return `${name} => ${this.convertTemplateLiteralPartsToPerl(value.parts)}`;
514
+ return `${perlHashKey(name)} => ${this.convertTemplateLiteralPartsToPerl(value.parts)}`;
504
515
  }
505
- return `${name} => ${this.convertExpressionToPerl(value.expr)}`;
516
+ return `${perlHashKey(name)} => ${this.convertExpressionToPerl(value.expr)}`;
506
517
  },
507
518
  emitSpread: (value) => {
508
519
  const perlExpr = this.convertExpressionToPerl(value.expr);
509
520
  return perlExpr.startsWith("%") ? perlExpr : `%{${perlExpr}}`;
510
521
  },
511
- emitTemplate: (value, name) => `${name} => ${this.convertTemplateLiteralPartsToPerl(value.parts)}`,
512
- emitBooleanAttr: (_value, name) => `${name} => 1`,
513
- emitBooleanShorthand: (_value, name) => `${name} => 1`,
522
+ emitTemplate: (value, name) => `${perlHashKey(name)} => ${this.convertTemplateLiteralPartsToPerl(value.parts)}`,
523
+ emitBooleanAttr: (_value, name) => `${perlHashKey(name)} => 1`,
524
+ emitBooleanShorthand: (_value, name) => `${perlHashKey(name)} => 1`,
514
525
  emitJsxChildren: () => ""
515
526
  };
516
527
  renderComponent(comp) {
@@ -582,6 +593,13 @@ ${children}`;
582
593
  if (this.refuseUnsupportedAttrExpression(value.expr, name)) {
583
594
  return "";
584
595
  }
596
+ const bareId = value.expr.trim();
597
+ const normalizedBareId = this.propsObjectName && bareId.startsWith(`${this.propsObjectName}.`) ? bareId.slice(this.propsObjectName.length + 1) : bareId;
598
+ if (!isBooleanAttr(name) && !value.presenceOrUndefined && /^[A-Za-z_$][\w$]*$/.test(normalizedBareId) && this.nullableOptionalProps.has(normalizedBareId)) {
599
+ const perl2 = this.convertExpressionToPerl(value.expr);
600
+ const body = isBooleanResultExpr(value.expr) || isAriaBooleanAttr(name) ? `${name}="<%= bf->bool_str(${perl2}) %>"` : `${name}="<%= ${perl2} %>"`;
601
+ return `<% if (defined ${perl2}) { %>${body}<% } %>`;
602
+ }
585
603
  if (isBooleanAttr(name) || value.presenceOrUndefined) {
586
604
  return `<%= ${this.convertExpressionToPerl(value.expr)} ? '${name}' : '' %>`;
587
605
  }
@@ -602,6 +620,22 @@ ${children}`;
602
620
  const entries = this.propsParams.map((p) => `${JSON.stringify(p.name)} => $${p.name}`);
603
621
  return `<%== bf->spread_attrs({${entries.join(", ")}}) %>`;
604
622
  }
623
+ const ternaryHashref = this.conditionalSpreadToPerl(trimmed);
624
+ if (ternaryHashref !== null) {
625
+ return `<%== bf->spread_attrs(${ternaryHashref}) %>`;
626
+ }
627
+ if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed)) {
628
+ const localConst = this.localConstants.find((c) => c.name === trimmed && !c.isModule);
629
+ if (localConst?.value !== undefined) {
630
+ const initTrimmed = localConst.value.trim();
631
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(initTrimmed)) {
632
+ const resolved = this.conditionalSpreadToPerl(initTrimmed);
633
+ if (resolved !== null) {
634
+ return `<%== bf->spread_attrs(${resolved}) %>`;
635
+ }
636
+ }
637
+ }
638
+ }
605
639
  const perlExpr = this.convertExpressionToPerl(value.expr);
606
640
  return `<%== bf->spread_attrs(${perlExpr}) %>`;
607
641
  },
@@ -766,6 +800,71 @@ ${reason}` : "";
766
800
  });
767
801
  return true;
768
802
  }
803
+ conditionalSpreadToPerl(expr) {
804
+ const sf = ts.createSourceFile("__spread.ts", `(${expr})`, ts.ScriptTarget.Latest, true);
805
+ if (sf.statements.length !== 1)
806
+ return null;
807
+ const stmt = sf.statements[0];
808
+ if (!ts.isExpressionStatement(stmt))
809
+ return null;
810
+ let node = stmt.expression;
811
+ while (ts.isParenthesizedExpression(node))
812
+ node = node.expression;
813
+ if (!ts.isConditionalExpression(node))
814
+ return null;
815
+ const unwrap = (e) => {
816
+ let n = e;
817
+ while (ts.isParenthesizedExpression(n))
818
+ n = n.expression;
819
+ return n;
820
+ };
821
+ const whenTrue = unwrap(node.whenTrue);
822
+ const whenFalse = unwrap(node.whenFalse);
823
+ if (!ts.isObjectLiteralExpression(whenTrue) || !ts.isObjectLiteralExpression(whenFalse)) {
824
+ return null;
825
+ }
826
+ const condPerl = this.convertExpressionToPerl(node.condition.getText(sf));
827
+ const truePerl = this.objectLiteralToPerlHashref(whenTrue, sf);
828
+ const falsePerl = this.objectLiteralToPerlHashref(whenFalse, sf);
829
+ if (truePerl === null || falsePerl === null)
830
+ return null;
831
+ return `${condPerl} ? ${truePerl} : ${falsePerl}`;
832
+ }
833
+ objectLiteralToPerlHashref(obj, sf) {
834
+ const entries = [];
835
+ for (const prop of obj.properties) {
836
+ if (!ts.isPropertyAssignment(prop))
837
+ return null;
838
+ let key;
839
+ if (ts.isIdentifier(prop.name)) {
840
+ key = prop.name.text;
841
+ } else if (ts.isStringLiteral(prop.name) || ts.isNoSubstitutionTemplateLiteral(prop.name)) {
842
+ key = prop.name.text;
843
+ } else {
844
+ return null;
845
+ }
846
+ const initNode = (() => {
847
+ let n = prop.initializer;
848
+ while (ts.isParenthesizedExpression(n))
849
+ n = n.expression;
850
+ return n;
851
+ })();
852
+ const indexed = this.recordIndexAccessToPerl(initNode);
853
+ const valPerl = indexed !== null ? indexed : this.convertExpressionToPerl(prop.initializer.getText(sf));
854
+ entries.push(`'${key.replace(/'/g, "\\'")}' => ${valPerl}`);
855
+ }
856
+ return entries.length === 0 ? "{}" : `{ ${entries.join(", ")} }`;
857
+ }
858
+ recordIndexAccessToPerl(val) {
859
+ const parsed = parseRecordIndexAccess(val, this.localConstants, this.propsParams);
860
+ if (!parsed)
861
+ return null;
862
+ const entries = parsed.entries.map((e) => {
863
+ const mapVal = e.value.kind === "number" ? e.value.text : `'${e.value.text.replace(/'/g, "\\'")}'`;
864
+ return `'${e.key.replace(/'/g, "\\'")}' => ${mapVal}`;
865
+ });
866
+ return `{ ${entries.join(", ")} }->{$${parsed.indexPropName}}`;
867
+ }
769
868
  convertExpressionToPerl(expr) {
770
869
  const trimmed = expr.trim();
771
870
  if (trimmed === "")
@@ -69,6 +69,14 @@ export declare class MojoAdapter extends BaseAdapter implements IRNodeEmitter<Mo
69
69
  * module-scope pure string literals qualify (see `collectModuleStringConsts`).
70
70
  */
71
71
  private moduleStringConsts;
72
+ /**
73
+ * Full local-constant metadata from the entry IR, kept so spread
74
+ * lowering can resolve a bare-identifier spread (`{...sizeAttrs}`) to
75
+ * its initializer text and a `Record[propKey]` spread value to the
76
+ * module-const object literal it indexes (#checkbox / icon). Populated
77
+ * at `generate()` entry alongside `moduleStringConsts`.
78
+ */
79
+ private localConstants;
72
80
  /**
73
81
  * Names currently bound by an enclosing loop body — the `my $<param>` and
74
82
  * `my $<index>` bindings `renderLoop` introduces — ref-counted so nested
@@ -78,6 +86,23 @@ export declare class MojoAdapter extends BaseAdapter implements IRNodeEmitter<Mo
78
86
  * loop-var shadowing guards). (#1749 review)
79
87
  */
80
88
  private loopBoundNames;
89
+ /**
90
+ * Prop names whose value is `undef` in the template body when the caller
91
+ * omits them — so a bare-reference attribute should be dropped rather
92
+ * than rendered as `attr=""`. The actual population criterion (see
93
+ * `generate()`) is: NO destructure default (`defaultValue === undefined`)
94
+ * AND non-rest (`!isRest`) AND non-primitive type (`type.kind !==
95
+ * 'primitive'`). It deliberately does NOT consult `p.optional`: the
96
+ * analyzer derives `optional` from the presence of a default initializer,
97
+ * not the `?` token, so it's not the right witness here. Excluding
98
+ * concrete primitives (`string`/`number`/`boolean`) mirrors the Go
99
+ * adapter's scope, which guards only `interface{}` (nillable) fields.
100
+ * Used by `elementAttrEmitter.emitExpression` to guard such an attribute
101
+ * with a Perl `defined $x` check (`<textarea>` omits `rows`), matching
102
+ * Hono's nullish-attribute omission. Concrete/defaulted props are
103
+ * excluded and always emit unconditionally.
104
+ */
105
+ private nullableOptionalProps;
81
106
  constructor(options?: MojoAdapterOptions);
82
107
  generate(ir: ComponentIR, options?: AdapterGenerateOptions): AdapterOutput;
83
108
  /**
@@ -202,6 +227,47 @@ export declare class MojoAdapter extends BaseAdapter implements IRNodeEmitter<Mo
202
227
  * should drop the attribute / skip the emit).
203
228
  */
204
229
  private refuseUnsupportedAttrExpression;
230
+ /**
231
+ * Lower a conditional inline-object spread expression
232
+ * `(COND ? { 'aria-describedby': describedBy } : {})`
233
+ * (either branch possibly `{}`) into a Perl inline ternary of
234
+ * hashrefs for `bf->spread_attrs`:
235
+ * `$describedBy ? { 'aria-describedby' => $describedBy } : {}`
236
+ *
237
+ * The condition is translated via `convertExpressionToPerl` (a bare
238
+ * prop ident becomes `$describedBy`; Perl truthiness handles the
239
+ * test). Object literals become Perl hashrefs with `=>`; string-
240
+ * literal keys are quoted, values resolve via `convertExpressionToPerl`.
241
+ *
242
+ * Returns null when the expression is NOT this shape, or when a part
243
+ * can't be faithfully lowered (non-static key, etc.) so the caller
244
+ * falls back to the standard `convertExpressionToPerl` path (which
245
+ * records BF101). Scoped strictly to ternary-of-object-literals so no
246
+ * other spread shape regresses.
247
+ */
248
+ private conditionalSpreadToPerl;
249
+ /**
250
+ * Convert a static object literal into a Perl hashref string for a
251
+ * conditional spread. Only static string/identifier keys are allowed;
252
+ * values resolve via `convertExpressionToPerl`. Returns null for any
253
+ * computed/spread/dynamic key. Empty object → `{}`.
254
+ */
255
+ private objectLiteralToPerlHashref;
256
+ /**
257
+ * Lower a spread-object VALUE of the form `IDENT[KEY]` where:
258
+ * - `IDENT` resolves via `localConstants` to a MODULE-scope object
259
+ * literal whose property values are all scalar (number/string)
260
+ * literals under static (string-literal or identifier) keys
261
+ * (a `Record<staticKeys, scalar>` map like `sizeMap`), AND
262
+ * - `KEY` is a bare identifier that is a prop.
263
+ * Emits an inline indexed Perl hashref:
264
+ * `{ 'sm' => 16, 'md' => 20, ... }->{$size}`
265
+ *
266
+ * Returns the Perl string when convertible, else `null` so the caller
267
+ * falls back to its normal value lowering (which records BF101 for an
268
+ * unsupported shape). Mirror of the Go adapter's `recordIndexAccessToGoMap`.
269
+ */
270
+ private recordIndexAccessToPerl;
205
271
  private convertExpressionToPerl;
206
272
  /**
207
273
  * Render a full ParsedExpr tree to Perl for top-level (non-filter)
@@ -1 +1 @@
1
- {"version":3,"file":"mojo-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/mojo-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mojo-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/mojo-adapter.ts"],"names":[],"mappings":"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"}
package/dist/build.js CHANGED
@@ -8,7 +8,10 @@ import {
8
8
  identifierPath,
9
9
  emitParsedExpr,
10
10
  emitIRNode,
11
- emitAttrValue
11
+ emitAttrValue,
12
+ augmentInheritedPropAccesses,
13
+ parseRecordIndexAccess,
14
+ evalStringArrayJoin
12
15
  } from "@barefootjs/jsx";
13
16
 
14
17
  // src/adapter/boolean-result.ts
@@ -95,7 +98,10 @@ function parsePureStringLiteral(source) {
95
98
  if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
96
99
  return init.text;
97
100
  }
98
- return null;
101
+ return evalStringArrayJoin(source);
102
+ }
103
+ function perlHashKey(name) {
104
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : `'${name.replace(/'/g, "\\'")}'`;
99
105
  }
100
106
 
101
107
  class MojoAdapter extends BaseAdapter {
@@ -112,7 +118,9 @@ class MojoAdapter extends BaseAdapter {
112
118
  propsParams = [];
113
119
  stringValueNames = new Set;
114
120
  moduleStringConsts = new Map;
121
+ localConstants = [];
115
122
  loopBoundNames = new Map;
123
+ nullableOptionalProps = new Set;
116
124
  constructor(options = {}) {
117
125
  super();
118
126
  this.options = {
@@ -123,7 +131,9 @@ class MojoAdapter extends BaseAdapter {
123
131
  generate(ir, options) {
124
132
  this.componentName = ir.metadata.componentName;
125
133
  this.propsObjectName = ir.metadata.propsObjectName ?? null;
134
+ augmentInheritedPropAccesses(ir);
126
135
  this.propsParams = ir.metadata.propsParams.map((p) => ({ name: p.name }));
136
+ this.nullableOptionalProps = new Set(ir.metadata.propsParams.filter((p) => p.defaultValue === undefined && !p.isRest && p.type?.kind !== "primitive").map((p) => p.name));
127
137
  this.stringValueNames = new Set;
128
138
  for (const s of ir.metadata.signals) {
129
139
  if (isStringTypeInfo(s.type) || isBareStringLiteral(s.initialValue)) {
@@ -135,6 +145,7 @@ class MojoAdapter extends BaseAdapter {
135
145
  this.stringValueNames.add(p.name);
136
146
  }
137
147
  this.moduleStringConsts = this.collectModuleStringConsts(ir.metadata.localConstants);
148
+ this.localConstants = ir.metadata.localConstants ?? [];
138
149
  this.loopBoundNames.clear();
139
150
  this.errors = [];
140
151
  this.childrenCaptureCounter = 0;
@@ -497,20 +508,20 @@ ${renderedChildren}` : renderedChildren;
497
508
  `);
498
509
  }
499
510
  componentPropEmitter = {
500
- emitLiteral: (value, name) => `${name} => '${value.value}'`,
511
+ emitLiteral: (value, name) => `${perlHashKey(name)} => '${value.value}'`,
501
512
  emitExpression: (value, name) => {
502
513
  if (value.parts) {
503
- return `${name} => ${this.convertTemplateLiteralPartsToPerl(value.parts)}`;
514
+ return `${perlHashKey(name)} => ${this.convertTemplateLiteralPartsToPerl(value.parts)}`;
504
515
  }
505
- return `${name} => ${this.convertExpressionToPerl(value.expr)}`;
516
+ return `${perlHashKey(name)} => ${this.convertExpressionToPerl(value.expr)}`;
506
517
  },
507
518
  emitSpread: (value) => {
508
519
  const perlExpr = this.convertExpressionToPerl(value.expr);
509
520
  return perlExpr.startsWith("%") ? perlExpr : `%{${perlExpr}}`;
510
521
  },
511
- emitTemplate: (value, name) => `${name} => ${this.convertTemplateLiteralPartsToPerl(value.parts)}`,
512
- emitBooleanAttr: (_value, name) => `${name} => 1`,
513
- emitBooleanShorthand: (_value, name) => `${name} => 1`,
522
+ emitTemplate: (value, name) => `${perlHashKey(name)} => ${this.convertTemplateLiteralPartsToPerl(value.parts)}`,
523
+ emitBooleanAttr: (_value, name) => `${perlHashKey(name)} => 1`,
524
+ emitBooleanShorthand: (_value, name) => `${perlHashKey(name)} => 1`,
514
525
  emitJsxChildren: () => ""
515
526
  };
516
527
  renderComponent(comp) {
@@ -582,6 +593,13 @@ ${children}`;
582
593
  if (this.refuseUnsupportedAttrExpression(value.expr, name)) {
583
594
  return "";
584
595
  }
596
+ const bareId = value.expr.trim();
597
+ const normalizedBareId = this.propsObjectName && bareId.startsWith(`${this.propsObjectName}.`) ? bareId.slice(this.propsObjectName.length + 1) : bareId;
598
+ if (!isBooleanAttr(name) && !value.presenceOrUndefined && /^[A-Za-z_$][\w$]*$/.test(normalizedBareId) && this.nullableOptionalProps.has(normalizedBareId)) {
599
+ const perl2 = this.convertExpressionToPerl(value.expr);
600
+ const body = isBooleanResultExpr(value.expr) || isAriaBooleanAttr(name) ? `${name}="<%= bf->bool_str(${perl2}) %>"` : `${name}="<%= ${perl2} %>"`;
601
+ return `<% if (defined ${perl2}) { %>${body}<% } %>`;
602
+ }
585
603
  if (isBooleanAttr(name) || value.presenceOrUndefined) {
586
604
  return `<%= ${this.convertExpressionToPerl(value.expr)} ? '${name}' : '' %>`;
587
605
  }
@@ -602,6 +620,22 @@ ${children}`;
602
620
  const entries = this.propsParams.map((p) => `${JSON.stringify(p.name)} => $${p.name}`);
603
621
  return `<%== bf->spread_attrs({${entries.join(", ")}}) %>`;
604
622
  }
623
+ const ternaryHashref = this.conditionalSpreadToPerl(trimmed);
624
+ if (ternaryHashref !== null) {
625
+ return `<%== bf->spread_attrs(${ternaryHashref}) %>`;
626
+ }
627
+ if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed)) {
628
+ const localConst = this.localConstants.find((c) => c.name === trimmed && !c.isModule);
629
+ if (localConst?.value !== undefined) {
630
+ const initTrimmed = localConst.value.trim();
631
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(initTrimmed)) {
632
+ const resolved = this.conditionalSpreadToPerl(initTrimmed);
633
+ if (resolved !== null) {
634
+ return `<%== bf->spread_attrs(${resolved}) %>`;
635
+ }
636
+ }
637
+ }
638
+ }
605
639
  const perlExpr = this.convertExpressionToPerl(value.expr);
606
640
  return `<%== bf->spread_attrs(${perlExpr}) %>`;
607
641
  },
@@ -766,6 +800,71 @@ ${reason}` : "";
766
800
  });
767
801
  return true;
768
802
  }
803
+ conditionalSpreadToPerl(expr) {
804
+ const sf = ts.createSourceFile("__spread.ts", `(${expr})`, ts.ScriptTarget.Latest, true);
805
+ if (sf.statements.length !== 1)
806
+ return null;
807
+ const stmt = sf.statements[0];
808
+ if (!ts.isExpressionStatement(stmt))
809
+ return null;
810
+ let node = stmt.expression;
811
+ while (ts.isParenthesizedExpression(node))
812
+ node = node.expression;
813
+ if (!ts.isConditionalExpression(node))
814
+ return null;
815
+ const unwrap = (e) => {
816
+ let n = e;
817
+ while (ts.isParenthesizedExpression(n))
818
+ n = n.expression;
819
+ return n;
820
+ };
821
+ const whenTrue = unwrap(node.whenTrue);
822
+ const whenFalse = unwrap(node.whenFalse);
823
+ if (!ts.isObjectLiteralExpression(whenTrue) || !ts.isObjectLiteralExpression(whenFalse)) {
824
+ return null;
825
+ }
826
+ const condPerl = this.convertExpressionToPerl(node.condition.getText(sf));
827
+ const truePerl = this.objectLiteralToPerlHashref(whenTrue, sf);
828
+ const falsePerl = this.objectLiteralToPerlHashref(whenFalse, sf);
829
+ if (truePerl === null || falsePerl === null)
830
+ return null;
831
+ return `${condPerl} ? ${truePerl} : ${falsePerl}`;
832
+ }
833
+ objectLiteralToPerlHashref(obj, sf) {
834
+ const entries = [];
835
+ for (const prop of obj.properties) {
836
+ if (!ts.isPropertyAssignment(prop))
837
+ return null;
838
+ let key;
839
+ if (ts.isIdentifier(prop.name)) {
840
+ key = prop.name.text;
841
+ } else if (ts.isStringLiteral(prop.name) || ts.isNoSubstitutionTemplateLiteral(prop.name)) {
842
+ key = prop.name.text;
843
+ } else {
844
+ return null;
845
+ }
846
+ const initNode = (() => {
847
+ let n = prop.initializer;
848
+ while (ts.isParenthesizedExpression(n))
849
+ n = n.expression;
850
+ return n;
851
+ })();
852
+ const indexed = this.recordIndexAccessToPerl(initNode);
853
+ const valPerl = indexed !== null ? indexed : this.convertExpressionToPerl(prop.initializer.getText(sf));
854
+ entries.push(`'${key.replace(/'/g, "\\'")}' => ${valPerl}`);
855
+ }
856
+ return entries.length === 0 ? "{}" : `{ ${entries.join(", ")} }`;
857
+ }
858
+ recordIndexAccessToPerl(val) {
859
+ const parsed = parseRecordIndexAccess(val, this.localConstants, this.propsParams);
860
+ if (!parsed)
861
+ return null;
862
+ const entries = parsed.entries.map((e) => {
863
+ const mapVal = e.value.kind === "number" ? e.value.text : `'${e.value.text.replace(/'/g, "\\'")}'`;
864
+ return `'${e.key.replace(/'/g, "\\'")}' => ${mapVal}`;
865
+ });
866
+ return `{ ${entries.join(", ")} }->{$${parsed.indexPropName}}`;
867
+ }
769
868
  convertExpressionToPerl(expr) {
770
869
  const trimmed = expr.trim();
771
870
  if (trimmed === "")