@barefootjs/erb 0.33.6 → 0.35.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.
@@ -17,13 +17,23 @@
17
17
  * genuinely needs it, so the seam documents the real cross-module coupling
18
18
  * rather than re-exposing the whole adapter.
19
19
  */
20
- import type { ParsedExpr, CompilerError, IRMetadata } from '@barefootjs/jsx';
20
+ import type { ParsedExpr, CompilerError, IRMetadata, LoweringMatcher } from '@barefootjs/jsx';
21
21
  export interface ErbEmitContext {
22
22
  /**
23
23
  * Local binding names the request-scoped `searchParams()` env signal
24
24
  * is imported under. Non-empty enables the env-signal method-call lowering.
25
25
  */
26
26
  readonly _searchParamsLocals: Set<string>;
27
+ /**
28
+ * Registered lowering-plugin matchers (#2057), bound to this component's
29
+ * metadata at init. Read by `ErbTopLevelEmitter`'s `lowering` seam (#2843)
30
+ * so a registered call — the built-in `queryHref`, or any userland plugin —
31
+ * is recognised no matter where it sits in an expression tree (a ternary
32
+ * branch, a template-literal interpolation, …), not only when it's the call
33
+ * the adapter's own top-level conversion entry point
34
+ * (`convertExpressionToRuby`) is asked to lower directly.
35
+ */
36
+ readonly _loweringMatchers: readonly LoweringMatcher[];
27
37
  /**
28
38
  * Inline a module-scope pure string-literal const by name as the resolved
29
39
  * literal value, or null when the name is not such a const.
@@ -1 +1 @@
1
- {"version":3,"file":"emit-context.d.ts","sourceRoot":"","sources":["../../src/adapter/emit-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE5E,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAEzC;;;OAGG;IACH,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAErD,mFAAmF;IACnF,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAEhD;;;OAGG;IACH,0BAA0B,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAE1E;;;;;;;;;OASG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAEtC,uEAAuE;IACvE,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAEzC,wDAAwD;IACxD,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAExD,wEAAwE;IACxE,2BAA2B,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;CACrE;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAE9B,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,CAAA;IAEhC,0EAA0E;IAC1E,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAC,gBAAgB,CAAC,CAAA;IAErD,sEAAsE;IACtE,QAAQ,CAAC,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAExC;;;;;;;;;OASG;IACH,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;CACtE;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;;;;;;OAYG;IACH,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,MAAM,CAAA;CAClG"}
1
+ {"version":3,"file":"emit-context.d.ts","sourceRoot":"","sources":["../../src/adapter/emit-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAE7F,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAEzC;;;;;;;;OAQG;IACH,QAAQ,CAAC,iBAAiB,EAAE,SAAS,eAAe,EAAE,CAAA;IAEtD;;;OAGG;IACH,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAErD,mFAAmF;IACnF,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAEhD;;;OAGG;IACH,0BAA0B,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAE1E;;;;;;;;;OASG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAEtC,uEAAuE;IACvE,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAEzC,wDAAwD;IACxD,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAExD,wEAAwE;IACxE,2BAA2B,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;CACrE;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAE9B,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,CAAA;IAEhC,0EAA0E;IAC1E,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAC,gBAAgB,CAAC,CAAA;IAErD,sEAAsE;IACtE,QAAQ,CAAC,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAExC;;;;;;;;;OASG;IACH,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;CACtE;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;;;;;;;;OAYG;IACH,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,MAAM,CAAA;CAClG"}
@@ -36,6 +36,7 @@
36
36
  */
37
37
  import type { ComponentIR, IRNode, IRElement, IRText, IRExpression, IRConditional, IRLoop, IRComponent, IRFragment, IRSlot, IRIfStatement, IRProvider, IRAsync, TemplatePrimitiveRegistry } from '@barefootjs/jsx';
38
38
  import { BaseAdapter, type AdapterOutput, type AdapterGenerateOptions, type IRNodeEmitter, type EmitIRNode } from '@barefootjs/jsx';
39
+ import type { ParsedExpr } from '@barefootjs/jsx';
39
40
  import type { ErbRenderCtx } from './lib/types.ts';
40
41
  export type { ErbAdapterOptions } from './lib/types.ts';
41
42
  import type { ErbAdapterOptions } from './lib/types.ts';
@@ -189,6 +190,8 @@ export declare class ErbAdapter extends BaseAdapter implements IRNodeEmitter<Erb
189
190
  parseUndefinedAlternateTernary(expr: string): {
190
191
  condition: string;
191
192
  consequent: string;
193
+ testParsed: ParsedExpr;
194
+ consequentParsed: ParsedExpr;
192
195
  } | null;
193
196
  /**
194
197
  * Inline a const (any scope) whose initializer is a pure numeric or
@@ -1 +1 @@
1
- {"version":3,"file":"erb-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/erb-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"erb-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/erb-adapter.ts"],"names":[],"mappings":"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"}
@@ -31,7 +31,7 @@
31
31
  * `nil`/`false`, so `0`, `''`, and `NaN` are Ruby-truthy but JS-falsy. Every
32
32
  * JS truthiness test in this file goes through `bf.truthy?` for that reason.
33
33
  */
34
- import { type ParsedExprEmitter, type ArrayMethod, type LiteralType, type ParsedExpr, type ObjectLiteralProperty, type FlatDepth, type TemplatePart } from '@barefootjs/jsx';
34
+ import { type ParsedExprEmitter, type ArrayMethod, type LiteralType, type ParsedExpr, type ObjectLiteralProperty, type FlatDepth, type TemplatePart, type LoweringEmitter } from '@barefootjs/jsx';
35
35
  import type { ErbEmitContext } from '../emit-context.ts';
36
36
  export declare class ErbFilterEmitter implements ParsedExprEmitter {
37
37
  private readonly param;
@@ -76,6 +76,16 @@ export declare class ErbFilterEmitter implements ParsedExprEmitter {
76
76
  export declare class ErbTopLevelEmitter implements ParsedExprEmitter {
77
77
  private readonly ctx;
78
78
  constructor(ctx: ErbEmitContext);
79
+ /**
80
+ * Registered-lowering seam (#2843): `emitParsedExpr`'s shared `call` case
81
+ * tries every matcher here BEFORE `call()` itself, so a registered call
82
+ * (the built-in `queryHref`, or any userland plugin) is recognised no
83
+ * matter where it sits in the tree. `render` is what used to live inline
84
+ * in `ErbAdapter.convertExpressionToRuby` before the object-literal
85
+ * support-gate refusal (`checkSupport`'s `call` arm, now itself
86
+ * registry-aware) made the pre-gate special case unnecessary.
87
+ */
88
+ get lowering(): LoweringEmitter;
79
89
  identifier(name: string): string;
80
90
  literal(value: string | number | boolean | null, literalType: LiteralType): string;
81
91
  member(object: ParsedExpr, property: string, _computed: boolean, optional: boolean, emit: (e: ParsedExpr) => string): string;
@@ -1 +1 @@
1
- {"version":3,"file":"emitters.d.ts","sourceRoot":"","sources":["../../../src/adapter/expr/emitters.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"emitters.d.ts","sourceRoot":"","sources":["../../../src/adapter/expr/emitters.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EACL,KAAK,iBAAiB,EAEtB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EACd,KAAK,YAAY,EAMjB,KAAK,eAAe,EAIrB,MAAM,iBAAiB,CAAA;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAwCxD,qBAAa,gBAAiB,YAAW,iBAAiB;IASxD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IAMjD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAwB;IAIzD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAwB;IAKrD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAA4C;IAa3E,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IAEtC,YACE,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,gBAAgB,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAqB,EACtD,YAAY,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAA4B,EACzD,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,EAC1D,aAAa,GAAE,MAAyB,EAQzC;IAED,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM/B;IAED,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,EAAE,WAAW,EAAE,WAAW,GAAG,MAAM,CAKjF;IAED,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAkB3H;IAED,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAE1F;IAED,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAMpF;IAED,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAM/E;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAa/F;IAED,OAAO,CAAC,EAAE,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAU5G;IAED,cAAc,CACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,EAC7C,SAAS,EAAE,UAAU,EAAE,EACvB,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAC9B,MAAM,CA0CR;IAED,YAAY,CAAC,QAAQ,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAE5E;IAED,WAAW,CACT,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,UAAU,EAAE,EAClB,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAC9B,MAAM,CAER;IAED,UAAU,CACR,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,SAAS,GAAG;QAAE,IAAI,EAAE,UAAU,CAAA;KAAE,EACvC,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAC9B,MAAM,CAER;IAED,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,GAAG,MAAM,CAEtF;IAED,eAAe,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,CAE9C;IAED,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAIpF;IAED,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1B;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjD;IAED,aAAa,CAAC,WAAW,EAAE,qBAAqB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAM1G;CACF;AAED;;;;;;;;GAQG;AACH,qBAAa,kBAAmB,YAAW,iBAAiB;IAG1D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAgB;IAEpC,YAAY,GAAG,EAAE,cAAc,EAE9B;IAED;;;;;;;;OAQG;IACH,IAAI,QAAQ,IAAI,eAAe,CAkB9B;IAED,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsB/B;IAED,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,EAAE,WAAW,EAAE,WAAW,GAAG,MAAM,CAKjF;IAED,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAyB3H;IAED,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAE1F;IAED,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CA0CpF;IAED,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAK/E;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAQ/F;IAED,OAAO,CAAC,EAAE,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAM5G;IAED,cAAc,CACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,EAC7C,QAAQ,EAAE,UAAU,EAAE,EACtB,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAC9B,MAAM,CAqER;IAED,OAAO,CAAC,eAAe;IAiDvB,YAAY,CAAC,QAAQ,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAK5E;IAED,WAAW,CACT,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,UAAU,EAAE,EAClB,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAC9B,MAAM,CAER;IAED,UAAU,CACR,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,SAAS,GAAG;QAAE,IAAI,EAAE,UAAU,CAAA;KAAE,EACvC,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAC9B,MAAM,CAER;IAED,WAAW,CACT,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,UAAU,EACrB,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAC9B,MAAM,CAGR;IAED,eAAe,CAAC,KAAK,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAiB9E;IAED,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAOpF;IAED,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE1B;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAKjD;IAED,aAAa,CAAC,UAAU,EAAE,qBAAqB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAAG,MAAM,CAqBxG;CACF"}
@@ -187307,8 +187307,6 @@ import {
187307
187307
  lookupStaticRecordLiteral,
187308
187308
  searchParamsLocalNames,
187309
187309
  prepareLoweringMatchers,
187310
- queryHrefArgs,
187311
- isValidHelperId,
187312
187310
  sortComparatorFromArrow as sortComparatorFromArrow2,
187313
187311
  isDangerousInnerHtmlAttr,
187314
187312
  resolveDangerousInnerHtml,
@@ -187711,7 +187709,9 @@ import {
187711
187709
  groupObjectLiteralSegments,
187712
187710
  identifierPath,
187713
187711
  matchSearchParamsMethodCall,
187714
- sortComparatorFromArrow
187712
+ sortComparatorFromArrow,
187713
+ queryHrefArgs,
187714
+ isValidHelperId
187715
187715
  } from "@barefootjs/jsx";
187716
187716
 
187717
187717
  // src/adapter/expr/operand.ts
@@ -187871,6 +187871,21 @@ class ErbTopLevelEmitter {
187871
187871
  constructor(ctx) {
187872
187872
  this.ctx = ctx;
187873
187873
  }
187874
+ get lowering() {
187875
+ return {
187876
+ matchers: this.ctx._loweringMatchers,
187877
+ render: (node, emit) => {
187878
+ if (node.kind === "guard-list" && node.helper === "query") {
187879
+ const qArgs = queryHrefArgs(node, emit);
187880
+ return `bf.query(${qArgs.join(", ")})`;
187881
+ }
187882
+ if (node.kind === "helper-call" && isValidHelperId(node.helper)) {
187883
+ return `bf.${node.helper}(${node.args.map(emit).join(", ")})`;
187884
+ }
187885
+ return null;
187886
+ }
187887
+ };
187888
+ }
187874
187889
  identifier(name) {
187875
187890
  if (name === "undefined" || name === "null")
187876
187891
  return "nil";
@@ -188430,7 +188445,9 @@ class ErbAdapter extends BaseAdapter {
188430
188445
  return null;
188431
188446
  return {
188432
188447
  condition: exprToString(parsed.test),
188433
- consequent: exprToString(parsed.consequent)
188448
+ consequent: exprToString(parsed.consequent),
188449
+ testParsed: parsed.test,
188450
+ consequentParsed: parsed.consequent
188434
188451
  };
188435
188452
  }
188436
188453
  resolveLiteralConst(name) {
@@ -188771,7 +188788,7 @@ ${whenTrue}
188771
188788
  const prevScope = this.scope;
188772
188789
  this.scope = prevScope.enterLoopRow(loop);
188773
188790
  const renderedChildren = this.renderChildren(loop.children);
188774
- const children = loop.bodyIsItemConditional && loop.key ? `<%= bf.comment("loop-i:" + bf.string(${this.convertExpressionToRuby(loop.key)})) %>
188791
+ const children = loop.bodyIsItemConditional && loop.key ? `<%= bf.comment("loop-i:" + bf.escape_comment_key(${this.convertExpressionToRuby(loop.key)})) %>
188775
188792
  ${renderedChildren}` : renderedChildren;
188776
188793
  const lines = [];
188777
188794
  lines.push(`<%= bf.comment("loop:${loop.markerId}") %>`);
@@ -188975,8 +188992,8 @@ ${children}`;
188975
188992
  {
188976
188993
  const m = this.parseUndefinedAlternateTernary(value.expr);
188977
188994
  if (m) {
188978
- const cond = this.convertExpressionToRuby(m.condition);
188979
- const val = this.convertExpressionToRuby(m.consequent);
188995
+ const cond = this.convertExpressionToRuby("", m.testParsed);
188996
+ const val = this.convertExpressionToRuby("", m.consequentParsed);
188980
188997
  return `<% if bf.truthy?(${cond}) %>${name}="<%= bf.h(${val}) %>"<% end %>`;
188981
188998
  }
188982
188999
  }
@@ -189106,7 +189123,7 @@ ${children}`;
189106
189123
  if (!startsAsObjectLiteral && !hasTaggedTemplate)
189107
189124
  return false;
189108
189125
  const parsed = parseExpression2(expr.trim());
189109
- const support = isSupported(parsed);
189126
+ const support = isSupported(parsed, { loweringMatchers: this._loweringMatchers });
189110
189127
  if (parsed.kind !== "unsupported" && support.supported)
189111
189128
  return false;
189112
189129
  const reason = support.reason ?? (parsed.kind === "unsupported" ? parsed.reason : undefined);
@@ -189126,6 +189143,7 @@ ${reason}` : "";
189126
189143
  get emitCtx() {
189127
189144
  return {
189128
189145
  _searchParamsLocals: this._searchParamsLocals,
189146
+ _loweringMatchers: this._loweringMatchers,
189129
189147
  resolveModuleStringConst: (name) => this.resolveModuleStringConst(name),
189130
189148
  resolveLiteralConst: (name) => this.resolveLiteralConst(name),
189131
189149
  resolveStaticRecordLiteral: (o, k) => this.resolveStaticRecordLiteral(o, k),
@@ -189157,20 +189175,8 @@ ${reason}` : "";
189157
189175
  return "''";
189158
189176
  parsed = parseExpression2(trimmed);
189159
189177
  }
189160
- if (parsed.kind === "call") {
189161
- for (const matcher of this._loweringMatchers) {
189162
- const node = matcher(parsed.callee, parsed.args);
189163
- if (node?.kind === "guard-list" && node.helper === "query") {
189164
- const argsRuby = queryHrefArgs(node, (n) => this.renderParsedExprToRuby(n));
189165
- return `bf.query(${argsRuby.join(", ")})`;
189166
- }
189167
- if (node?.kind === "helper-call" && isValidHelperId(node.helper)) {
189168
- const argsX = node.args.map((a) => this.renderParsedExprToRuby(a));
189169
- return `bf.${node.helper}(${argsX.join(", ")})`;
189170
- }
189171
- }
189172
- }
189173
- const support = pos === "value" ? isSupportedValue(parsed) : isSupported(parsed);
189178
+ const supportOpts = { loweringMatchers: this._loweringMatchers };
189179
+ const support = pos === "value" ? isSupportedValue(parsed, supportOpts) : isSupported(parsed, supportOpts);
189174
189180
  if (!support.supported) {
189175
189181
  this.errors.push({
189176
189182
  code: "BF101",
@@ -1 +1 @@
1
- {"version":3,"file":"conformance-pins.d.ts","sourceRoot":"","sources":["../src/conformance-pins.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEtD,eAAO,MAAM,eAAe,EAAE,eAsD7B,CAAA"}
1
+ {"version":3,"file":"conformance-pins.d.ts","sourceRoot":"","sources":["../src/conformance-pins.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEtD,eAAO,MAAM,eAAe,EAAE,eA0D7B,CAAA"}
package/dist/index.js CHANGED
@@ -187307,8 +187307,6 @@ import {
187307
187307
  lookupStaticRecordLiteral,
187308
187308
  searchParamsLocalNames,
187309
187309
  prepareLoweringMatchers,
187310
- queryHrefArgs,
187311
- isValidHelperId,
187312
187310
  sortComparatorFromArrow as sortComparatorFromArrow2,
187313
187311
  isDangerousInnerHtmlAttr,
187314
187312
  resolveDangerousInnerHtml,
@@ -187711,7 +187709,9 @@ import {
187711
187709
  groupObjectLiteralSegments,
187712
187710
  identifierPath,
187713
187711
  matchSearchParamsMethodCall,
187714
- sortComparatorFromArrow
187712
+ sortComparatorFromArrow,
187713
+ queryHrefArgs,
187714
+ isValidHelperId
187715
187715
  } from "@barefootjs/jsx";
187716
187716
 
187717
187717
  // src/adapter/expr/operand.ts
@@ -187871,6 +187871,21 @@ class ErbTopLevelEmitter {
187871
187871
  constructor(ctx) {
187872
187872
  this.ctx = ctx;
187873
187873
  }
187874
+ get lowering() {
187875
+ return {
187876
+ matchers: this.ctx._loweringMatchers,
187877
+ render: (node, emit) => {
187878
+ if (node.kind === "guard-list" && node.helper === "query") {
187879
+ const qArgs = queryHrefArgs(node, emit);
187880
+ return `bf.query(${qArgs.join(", ")})`;
187881
+ }
187882
+ if (node.kind === "helper-call" && isValidHelperId(node.helper)) {
187883
+ return `bf.${node.helper}(${node.args.map(emit).join(", ")})`;
187884
+ }
187885
+ return null;
187886
+ }
187887
+ };
187888
+ }
187874
187889
  identifier(name) {
187875
187890
  if (name === "undefined" || name === "null")
187876
187891
  return "nil";
@@ -188430,7 +188445,9 @@ class ErbAdapter extends BaseAdapter {
188430
188445
  return null;
188431
188446
  return {
188432
188447
  condition: exprToString(parsed.test),
188433
- consequent: exprToString(parsed.consequent)
188448
+ consequent: exprToString(parsed.consequent),
188449
+ testParsed: parsed.test,
188450
+ consequentParsed: parsed.consequent
188434
188451
  };
188435
188452
  }
188436
188453
  resolveLiteralConst(name) {
@@ -188771,7 +188788,7 @@ ${whenTrue}
188771
188788
  const prevScope = this.scope;
188772
188789
  this.scope = prevScope.enterLoopRow(loop);
188773
188790
  const renderedChildren = this.renderChildren(loop.children);
188774
- const children = loop.bodyIsItemConditional && loop.key ? `<%= bf.comment("loop-i:" + bf.string(${this.convertExpressionToRuby(loop.key)})) %>
188791
+ const children = loop.bodyIsItemConditional && loop.key ? `<%= bf.comment("loop-i:" + bf.escape_comment_key(${this.convertExpressionToRuby(loop.key)})) %>
188775
188792
  ${renderedChildren}` : renderedChildren;
188776
188793
  const lines = [];
188777
188794
  lines.push(`<%= bf.comment("loop:${loop.markerId}") %>`);
@@ -188975,8 +188992,8 @@ ${children}`;
188975
188992
  {
188976
188993
  const m = this.parseUndefinedAlternateTernary(value.expr);
188977
188994
  if (m) {
188978
- const cond = this.convertExpressionToRuby(m.condition);
188979
- const val = this.convertExpressionToRuby(m.consequent);
188995
+ const cond = this.convertExpressionToRuby("", m.testParsed);
188996
+ const val = this.convertExpressionToRuby("", m.consequentParsed);
188980
188997
  return `<% if bf.truthy?(${cond}) %>${name}="<%= bf.h(${val}) %>"<% end %>`;
188981
188998
  }
188982
188999
  }
@@ -189106,7 +189123,7 @@ ${children}`;
189106
189123
  if (!startsAsObjectLiteral && !hasTaggedTemplate)
189107
189124
  return false;
189108
189125
  const parsed = parseExpression2(expr.trim());
189109
- const support = isSupported(parsed);
189126
+ const support = isSupported(parsed, { loweringMatchers: this._loweringMatchers });
189110
189127
  if (parsed.kind !== "unsupported" && support.supported)
189111
189128
  return false;
189112
189129
  const reason = support.reason ?? (parsed.kind === "unsupported" ? parsed.reason : undefined);
@@ -189126,6 +189143,7 @@ ${reason}` : "";
189126
189143
  get emitCtx() {
189127
189144
  return {
189128
189145
  _searchParamsLocals: this._searchParamsLocals,
189146
+ _loweringMatchers: this._loweringMatchers,
189129
189147
  resolveModuleStringConst: (name) => this.resolveModuleStringConst(name),
189130
189148
  resolveLiteralConst: (name) => this.resolveLiteralConst(name),
189131
189149
  resolveStaticRecordLiteral: (o, k) => this.resolveStaticRecordLiteral(o, k),
@@ -189157,20 +189175,8 @@ ${reason}` : "";
189157
189175
  return "''";
189158
189176
  parsed = parseExpression2(trimmed);
189159
189177
  }
189160
- if (parsed.kind === "call") {
189161
- for (const matcher of this._loweringMatchers) {
189162
- const node = matcher(parsed.callee, parsed.args);
189163
- if (node?.kind === "guard-list" && node.helper === "query") {
189164
- const argsRuby = queryHrefArgs(node, (n) => this.renderParsedExprToRuby(n));
189165
- return `bf.query(${argsRuby.join(", ")})`;
189166
- }
189167
- if (node?.kind === "helper-call" && isValidHelperId(node.helper)) {
189168
- const argsX = node.args.map((a) => this.renderParsedExprToRuby(a));
189169
- return `bf.${node.helper}(${argsX.join(", ")})`;
189170
- }
189171
- }
189172
- }
189173
- const support = pos === "value" ? isSupportedValue(parsed) : isSupported(parsed);
189178
+ const supportOpts = { loweringMatchers: this._loweringMatchers };
189179
+ const support = pos === "value" ? isSupportedValue(parsed, supportOpts) : isSupported(parsed, supportOpts);
189174
189180
  if (!support.supported) {
189175
189181
  this.errors.push({
189176
189182
  code: "BF101",
@@ -189256,9 +189262,7 @@ var conformancePins = {
189256
189262
  "namespace-import-primitive": [{ code: "BF013", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2771" }]
189257
189263
  };
189258
189264
  // src/render-divergences.ts
189259
- var renderDivergences = {
189260
- "aliased-loop-source": "A `.map()` loop whose source is a local const alias of a signal getter (`const items__alias = items`) throws `undefined method 'length' for nil (NoMethodError)` on real Ruby ERB — the seeded loop data is keyed by the signal's real name (`items`), and the alias hop is never resolved when deciding what to seed under `items__alias`. This is the SSR-side twin of #2778 (fixed for the CSR client-JS template in the same PR that added this fixture) — that fix only touches client-JS emission, not SSR data-seeding. Tracked at https://github.com/piconic-ai/barefootjs/issues/2813; graduate by resolving the alias hop at SSR-seeding time using the same `resolveAliasOrigin`/`resolveGetterAliases` mechanism #2778 introduced, rather than a third alias-hop walker."
189261
- };
189265
+ var renderDivergences = {};
189262
189266
  export {
189263
189267
  renderDivergences,
189264
189268
  erbAdapter,
@@ -1 +1 @@
1
- {"version":3,"file":"render-divergences.d.ts","sourceRoot":"","sources":["../src/render-divergences.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAQxD,eAAO,MAAM,iBAAiB,EAAE,iBAG/B,CAAA"}
1
+ {"version":3,"file":"render-divergences.d.ts","sourceRoot":"","sources":["../src/render-divergences.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAQxD,eAAO,MAAM,iBAAiB,EAAE,iBAAsB,CAAA"}
package/dist/vite.js CHANGED
@@ -12,6 +12,41 @@ import ts10 from "typescript";
12
12
 
13
13
  // ../jsx/src/expression-parser.ts
14
14
  import ts from "typescript";
15
+
16
+ // ../jsx/src/lowering-registry.ts
17
+ var plugins = [];
18
+ function registerLoweringPlugin(plugin) {
19
+ const existing = plugins.findIndex((p) => p.name === plugin.name);
20
+ if (existing >= 0)
21
+ plugins[existing] = plugin;
22
+ else
23
+ plugins.push(plugin);
24
+ }
25
+ function prepareLoweringMatchers(metadata) {
26
+ const matchers = [];
27
+ for (const plugin of plugins) {
28
+ const matcher = plugin.prepare(metadata);
29
+ if (matcher)
30
+ matchers.push(matcher);
31
+ }
32
+ return matchers;
33
+ }
34
+ function loweringNodeChildren(node) {
35
+ if (node.kind === "helper-call")
36
+ return [...node.args];
37
+ const children = [node.base];
38
+ for (const t of node.triples) {
39
+ if (t.guard)
40
+ children.push(t.guard);
41
+ children.push(t.value);
42
+ }
43
+ return children;
44
+ }
45
+ function isValidHelperId(helper) {
46
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(helper);
47
+ }
48
+
49
+ // ../jsx/src/expression-parser.ts
15
50
  var UNSUPPORTED_METHODS = new Set([
16
51
  "filter",
17
52
  "map",
@@ -263,7 +298,7 @@ function convertNode(node, raw) {
263
298
  }
264
299
  if (n === undefined || Number.isNaN(n)) {
265
300
  const parsedDepth = convertNode(depthNode, raw);
266
- if (checkSupport(parsedDepth, "rendered").supported) {
301
+ if (checkSupport(parsedDepth, "rendered", []).supported) {
267
302
  depthExpr = parsedDepth;
268
303
  flatDepth = 1;
269
304
  } else {
@@ -1041,13 +1076,13 @@ function getUnaryOperatorString(op) {
1041
1076
  return "unknown";
1042
1077
  }
1043
1078
  }
1044
- function isSupported(expr) {
1045
- return checkSupport(expr, "rendered");
1079
+ function isSupported(expr, opts) {
1080
+ return checkSupport(expr, "rendered", opts?.loweringMatchers ?? []);
1046
1081
  }
1047
- function isSupportedValue(expr) {
1048
- return checkSupport(expr, "value");
1082
+ function isSupportedValue(expr, opts) {
1083
+ return checkSupport(expr, "value", opts?.loweringMatchers ?? []);
1049
1084
  }
1050
- function checkSupport(expr, pos) {
1085
+ function checkSupport(expr, pos, matchers) {
1051
1086
  switch (expr.kind) {
1052
1087
  case "unsupported":
1053
1088
  return { supported: false, reason: expr.reason };
@@ -1056,7 +1091,7 @@ function checkSupport(expr, pos) {
1056
1091
  return { supported: false, reason: "Unsupported syntax: ObjectLiteralExpression" };
1057
1092
  }
1058
1093
  for (const prop of expr.properties) {
1059
- const propSupport = checkSupport(prop.kind === "spread" ? prop.expr : prop.value, pos);
1094
+ const propSupport = checkSupport(prop.kind === "spread" ? prop.expr : prop.value, pos, matchers);
1060
1095
  if (!propSupport.supported)
1061
1096
  return propSupport;
1062
1097
  }
@@ -1071,7 +1106,7 @@ function checkSupport(expr, pos) {
1071
1106
  return { supported: false, reason: "Standalone arrow functions / regex literals are not supported" };
1072
1107
  case "array-literal": {
1073
1108
  for (const el of expr.elements) {
1074
- const elSupport = checkSupport(el, pos);
1109
+ const elSupport = checkSupport(el, pos, matchers);
1075
1110
  if (!elSupport.supported)
1076
1111
  return elSupport;
1077
1112
  }
@@ -1084,28 +1119,39 @@ function checkSupport(expr, pos) {
1084
1119
  reason: `String.prototype.${expr.method} supports only a string pattern + string replacement (the regex form is deferred); use a string pattern or wrap the expression in /* @client */`
1085
1120
  };
1086
1121
  }
1087
- const objSupport = checkSupport(expr.object, pos);
1122
+ const objSupport = checkSupport(expr.object, pos, matchers);
1088
1123
  if (!objSupport.supported)
1089
1124
  return objSupport;
1090
1125
  for (const arg of expr.args) {
1091
- const argSupport = checkSupport(arg, pos);
1126
+ const argSupport = checkSupport(arg, pos, matchers);
1092
1127
  if (!argSupport.supported)
1093
1128
  return argSupport;
1094
1129
  }
1095
1130
  if (expr.method === "flat" && expr.depthExpr) {
1096
- const depthSupport = checkSupport(expr.depthExpr, pos);
1131
+ const depthSupport = checkSupport(expr.depthExpr, pos, matchers);
1097
1132
  if (!depthSupport.supported)
1098
1133
  return depthSupport;
1099
1134
  }
1100
1135
  return { supported: true, level: "L2" };
1101
1136
  }
1102
1137
  case "call": {
1138
+ for (const matcher of matchers) {
1139
+ const node = matcher(expr.callee, expr.args);
1140
+ if (!node)
1141
+ continue;
1142
+ for (const child of loweringNodeChildren(node)) {
1143
+ const childSupport = checkSupport(child, pos, matchers);
1144
+ if (!childSupport.supported)
1145
+ return childSupport;
1146
+ }
1147
+ return { supported: true, level: "L2" };
1148
+ }
1103
1149
  const cb = asCallbackMethodCall(expr);
1104
1150
  if (cb) {
1105
- const objSupport = checkSupport(cb.object, pos);
1151
+ const objSupport = checkSupport(cb.object, pos, matchers);
1106
1152
  if (!objSupport.supported)
1107
1153
  return objSupport;
1108
- const bodySupport = checkSupport(cb.arrow.body, pos);
1154
+ const bodySupport = checkSupport(cb.arrow.body, pos, matchers);
1109
1155
  if (!bodySupport.supported) {
1110
1156
  return {
1111
1157
  supported: false,
@@ -1114,13 +1160,13 @@ function checkSupport(expr, pos) {
1114
1160
  };
1115
1161
  }
1116
1162
  for (const rest of cb.args) {
1117
- const restSupport = checkSupport(rest, pos);
1163
+ const restSupport = checkSupport(rest, pos, matchers);
1118
1164
  if (!restSupport.supported)
1119
1165
  return restSupport;
1120
1166
  }
1121
1167
  return { supported: true, level: "L5" };
1122
1168
  }
1123
- const calleeSupport = checkSupport(expr.callee, pos);
1169
+ const calleeSupport = checkSupport(expr.callee, pos, matchers);
1124
1170
  if (!calleeSupport.supported) {
1125
1171
  return calleeSupport;
1126
1172
  }
@@ -1139,7 +1185,7 @@ function checkSupport(expr, pos) {
1139
1185
  return { supported: true, level: "L1" };
1140
1186
  }
1141
1187
  for (const arg of expr.args) {
1142
- const argSupport = checkSupport(arg, pos);
1188
+ const argSupport = checkSupport(arg, pos, matchers);
1143
1189
  if (!argSupport.supported) {
1144
1190
  return argSupport;
1145
1191
  }
@@ -1147,7 +1193,7 @@ function checkSupport(expr, pos) {
1147
1193
  return { supported: true, level: "L2" };
1148
1194
  }
1149
1195
  case "member": {
1150
- const objSupport = checkSupport(expr.object, pos);
1196
+ const objSupport = checkSupport(expr.object, pos, matchers);
1151
1197
  if (!objSupport.supported) {
1152
1198
  return objSupport;
1153
1199
  }
@@ -1157,19 +1203,19 @@ function checkSupport(expr, pos) {
1157
1203
  return { supported: true, level: "L2" };
1158
1204
  }
1159
1205
  case "index-access": {
1160
- const objSupport = checkSupport(expr.object, pos);
1206
+ const objSupport = checkSupport(expr.object, pos, matchers);
1161
1207
  if (!objSupport.supported)
1162
1208
  return objSupport;
1163
- const indexSupport = checkSupport(expr.index, pos);
1209
+ const indexSupport = checkSupport(expr.index, pos, matchers);
1164
1210
  if (!indexSupport.supported)
1165
1211
  return indexSupport;
1166
1212
  return { supported: true, level: "L2" };
1167
1213
  }
1168
1214
  case "binary": {
1169
- const leftSupport = checkSupport(expr.left, pos);
1215
+ const leftSupport = checkSupport(expr.left, pos, matchers);
1170
1216
  if (!leftSupport.supported)
1171
1217
  return leftSupport;
1172
- const rightSupport = checkSupport(expr.right, pos);
1218
+ const rightSupport = checkSupport(expr.right, pos, matchers);
1173
1219
  if (!rightSupport.supported)
1174
1220
  return rightSupport;
1175
1221
  if (["===", "==", "!==", "!=", ">", "<", ">=", "<="].includes(expr.op)) {
@@ -1181,7 +1227,7 @@ function checkSupport(expr, pos) {
1181
1227
  return { supported: false, reason: `Unknown operator: ${expr.op}` };
1182
1228
  }
1183
1229
  case "unary": {
1184
- const argSupport = checkSupport(expr.argument, pos);
1230
+ const argSupport = checkSupport(expr.argument, pos, matchers);
1185
1231
  if (!argSupport.supported)
1186
1232
  return argSupport;
1187
1233
  if (expr.op === "!") {
@@ -1193,25 +1239,25 @@ function checkSupport(expr, pos) {
1193
1239
  return { supported: false, reason: `Unsupported unary operator: ${expr.op}` };
1194
1240
  }
1195
1241
  case "logical": {
1196
- const leftSupport = checkSupport(expr.left, pos);
1242
+ const leftSupport = checkSupport(expr.left, pos, matchers);
1197
1243
  if (!leftSupport.supported)
1198
1244
  return leftSupport;
1199
1245
  if (expr.op === "??" && expr.right.kind === "object-literal" && expr.right.properties.length === 0) {
1200
1246
  return { supported: true, level: "L4" };
1201
1247
  }
1202
- const rightSupport = checkSupport(expr.right, pos);
1248
+ const rightSupport = checkSupport(expr.right, pos, matchers);
1203
1249
  if (!rightSupport.supported)
1204
1250
  return rightSupport;
1205
1251
  return { supported: true, level: "L4" };
1206
1252
  }
1207
1253
  case "conditional": {
1208
- const testSupport = checkSupport(expr.test, pos);
1254
+ const testSupport = checkSupport(expr.test, pos, matchers);
1209
1255
  if (!testSupport.supported)
1210
1256
  return testSupport;
1211
- const consSupport = checkSupport(expr.consequent, pos);
1257
+ const consSupport = checkSupport(expr.consequent, pos, matchers);
1212
1258
  if (!consSupport.supported)
1213
1259
  return consSupport;
1214
- const altSupport = checkSupport(expr.alternate, pos);
1260
+ const altSupport = checkSupport(expr.alternate, pos, matchers);
1215
1261
  if (!altSupport.supported)
1216
1262
  return altSupport;
1217
1263
  return { supported: true, level: "L4" };
@@ -1219,7 +1265,7 @@ function checkSupport(expr, pos) {
1219
1265
  case "template-literal": {
1220
1266
  for (const part of expr.parts) {
1221
1267
  if (part.type === "expression") {
1222
- const partSupport = checkSupport(part.expr, pos);
1268
+ const partSupport = checkSupport(part.expr, pos, matchers);
1223
1269
  if (!partSupport.supported)
1224
1270
  return partSupport;
1225
1271
  }
@@ -2228,6 +2274,12 @@ class BindingScope {
2228
2274
  }
2229
2275
  }
2230
2276
 
2277
+ // ../jsx/src/ir-to-client-js/safe-html.ts
2278
+ function safeHtml(expr) {
2279
+ return expr;
2280
+ }
2281
+ var EMPTY_MARKUP = safeHtml("''");
2282
+
2231
2283
  // ../jsx/src/ir-to-client-js/html-template.ts
2232
2284
  var VOID_ELEMENTS = new Set([
2233
2285
  "area",
@@ -2514,6 +2566,7 @@ var CLIENT_EXPORTS = new Set([
2514
2566
  "isSSRPortal",
2515
2567
  "findSiblingSlot",
2516
2568
  "cleanupPortalPlaceholder",
2569
+ "trackPosition",
2517
2570
  "createSearchParams",
2518
2571
  "queryHref",
2519
2572
  "formatDate",
@@ -2574,7 +2627,8 @@ var BROWSER_ONLY_CLIENT_APIS = new Set([
2574
2627
  "createPortal",
2575
2628
  "isSSRPortal",
2576
2629
  "findSiblingSlot",
2577
- "cleanupPortalPlaceholder"
2630
+ "cleanupPortalPlaceholder",
2631
+ "trackPosition"
2578
2632
  ]);
2579
2633
  var REACTIVE_PRIMITIVES = new Set([
2580
2634
  "createSignal",
@@ -3173,30 +3227,6 @@ import ts16 from "typescript";
3173
3227
 
3174
3228
  // ../jsx/src/relocate.ts
3175
3229
  import ts18 from "typescript";
3176
-
3177
- // ../jsx/src/lowering-registry.ts
3178
- var plugins = [];
3179
- function registerLoweringPlugin(plugin) {
3180
- const existing = plugins.findIndex((p) => p.name === plugin.name);
3181
- if (existing >= 0)
3182
- plugins[existing] = plugin;
3183
- else
3184
- plugins.push(plugin);
3185
- }
3186
- function prepareLoweringMatchers(metadata) {
3187
- const matchers = [];
3188
- for (const plugin of plugins) {
3189
- const matcher = plugin.prepare(metadata);
3190
- if (matcher)
3191
- matchers.push(matcher);
3192
- }
3193
- return matchers;
3194
- }
3195
- function isValidHelperId(helper) {
3196
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(helper);
3197
- }
3198
-
3199
- // ../jsx/src/relocate.ts
3200
3230
  var REGISTRY_SAFE_BINDING_KINDS = new Set([
3201
3231
  "global",
3202
3232
  "module-import",
@@ -4660,6 +4690,17 @@ function emitParsedExpr(expr, emitter) {
4660
4690
  case "literal":
4661
4691
  return emitter.literal(expr.value, expr.literalType);
4662
4692
  case "call": {
4693
+ if (emitter.lowering) {
4694
+ for (const matcher of emitter.lowering.matchers) {
4695
+ const node = matcher(expr.callee, expr.args);
4696
+ if (!node)
4697
+ continue;
4698
+ const rendered = emitter.lowering.render(node, emit);
4699
+ if (rendered !== null)
4700
+ return rendered;
4701
+ break;
4702
+ }
4703
+ }
4663
4704
  const cb = asCallbackMethodCall(expr);
4664
4705
  if (cb)
4665
4706
  return emitter.callbackMethod(cb.method, cb.object, cb.arrow, cb.args, emit);
@@ -5717,6 +5758,21 @@ class ErbTopLevelEmitter {
5717
5758
  constructor(ctx) {
5718
5759
  this.ctx = ctx;
5719
5760
  }
5761
+ get lowering() {
5762
+ return {
5763
+ matchers: this.ctx._loweringMatchers,
5764
+ render: (node, emit) => {
5765
+ if (node.kind === "guard-list" && node.helper === "query") {
5766
+ const qArgs = queryHrefArgs(node, emit);
5767
+ return `bf.query(${qArgs.join(", ")})`;
5768
+ }
5769
+ if (node.kind === "helper-call" && isValidHelperId(node.helper)) {
5770
+ return `bf.${node.helper}(${node.args.map(emit).join(", ")})`;
5771
+ }
5772
+ return null;
5773
+ }
5774
+ };
5775
+ }
5720
5776
  identifier(name) {
5721
5777
  if (name === "undefined" || name === "null")
5722
5778
  return "nil";
@@ -6271,7 +6327,9 @@ class ErbAdapter extends BaseAdapter {
6271
6327
  return null;
6272
6328
  return {
6273
6329
  condition: exprToString(parsed.test),
6274
- consequent: exprToString(parsed.consequent)
6330
+ consequent: exprToString(parsed.consequent),
6331
+ testParsed: parsed.test,
6332
+ consequentParsed: parsed.consequent
6275
6333
  };
6276
6334
  }
6277
6335
  resolveLiteralConst(name) {
@@ -6612,7 +6670,7 @@ ${whenTrue}
6612
6670
  const prevScope = this.scope;
6613
6671
  this.scope = prevScope.enterLoopRow(loop);
6614
6672
  const renderedChildren = this.renderChildren(loop.children);
6615
- const children = loop.bodyIsItemConditional && loop.key ? `<%= bf.comment("loop-i:" + bf.string(${this.convertExpressionToRuby(loop.key)})) %>
6673
+ const children = loop.bodyIsItemConditional && loop.key ? `<%= bf.comment("loop-i:" + bf.escape_comment_key(${this.convertExpressionToRuby(loop.key)})) %>
6616
6674
  ${renderedChildren}` : renderedChildren;
6617
6675
  const lines = [];
6618
6676
  lines.push(`<%= bf.comment("loop:${loop.markerId}") %>`);
@@ -6816,8 +6874,8 @@ ${children}`;
6816
6874
  {
6817
6875
  const m = this.parseUndefinedAlternateTernary(value.expr);
6818
6876
  if (m) {
6819
- const cond = this.convertExpressionToRuby(m.condition);
6820
- const val = this.convertExpressionToRuby(m.consequent);
6877
+ const cond = this.convertExpressionToRuby("", m.testParsed);
6878
+ const val = this.convertExpressionToRuby("", m.consequentParsed);
6821
6879
  return `<% if bf.truthy?(${cond}) %>${name}="<%= bf.h(${val}) %>"<% end %>`;
6822
6880
  }
6823
6881
  }
@@ -6947,7 +7005,7 @@ ${children}`;
6947
7005
  if (!startsAsObjectLiteral && !hasTaggedTemplate)
6948
7006
  return false;
6949
7007
  const parsed = parseExpression(expr.trim());
6950
- const support = isSupported(parsed);
7008
+ const support = isSupported(parsed, { loweringMatchers: this._loweringMatchers });
6951
7009
  if (parsed.kind !== "unsupported" && support.supported)
6952
7010
  return false;
6953
7011
  const reason = support.reason ?? (parsed.kind === "unsupported" ? parsed.reason : undefined);
@@ -6967,6 +7025,7 @@ ${reason}` : "";
6967
7025
  get emitCtx() {
6968
7026
  return {
6969
7027
  _searchParamsLocals: this._searchParamsLocals,
7028
+ _loweringMatchers: this._loweringMatchers,
6970
7029
  resolveModuleStringConst: (name) => this.resolveModuleStringConst(name),
6971
7030
  resolveLiteralConst: (name) => this.resolveLiteralConst(name),
6972
7031
  resolveStaticRecordLiteral: (o, k) => this.resolveStaticRecordLiteral(o, k),
@@ -6998,20 +7057,8 @@ ${reason}` : "";
6998
7057
  return "''";
6999
7058
  parsed = parseExpression(trimmed);
7000
7059
  }
7001
- if (parsed.kind === "call") {
7002
- for (const matcher of this._loweringMatchers) {
7003
- const node = matcher(parsed.callee, parsed.args);
7004
- if (node?.kind === "guard-list" && node.helper === "query") {
7005
- const argsRuby = queryHrefArgs(node, (n) => this.renderParsedExprToRuby(n));
7006
- return `bf.query(${argsRuby.join(", ")})`;
7007
- }
7008
- if (node?.kind === "helper-call" && isValidHelperId(node.helper)) {
7009
- const argsX = node.args.map((a) => this.renderParsedExprToRuby(a));
7010
- return `bf.${node.helper}(${argsX.join(", ")})`;
7011
- }
7012
- }
7013
- }
7014
- const support = pos === "value" ? isSupportedValue(parsed) : isSupported(parsed);
7060
+ const supportOpts = { loweringMatchers: this._loweringMatchers };
7061
+ const support = pos === "value" ? isSupportedValue(parsed, supportOpts) : isSupported(parsed, supportOpts);
7015
7062
  if (!support.supported) {
7016
7063
  this.errors.push({
7017
7064
  code: "BF101",
@@ -184,6 +184,22 @@ module BarefootJS
184
184
  "<!--bf-#{text}-->"
185
185
  end
186
186
 
187
+ # Neutralize a value for splicing into `comment`'s HTML comment content
188
+ # (#2795 follow-up). `comment` itself does no escaping -- fine for every
189
+ # other caller (marker IDs like "cond-start:s0", "loop:l0", ...), which
190
+ # are entirely compiler-generated, but the whole-item-conditional loop's
191
+ # "loop-i:<key>" anchor carries a user-controlled key. Standard HTML
192
+ # escaping doesn't help inside a comment -- only the literal sequence
193
+ # "-->" terminates it early, and &/</>/"/' are not special there. The
194
+ # key's exact text doesn't need to round-trip (the client's
195
+ # mapArrayAnchored matches items positionally and by its own
196
+ # JS-computed key, never by re-parsing the anchor Comment.nodeValue), so
197
+ # replacing every "-" with the visually-similar U+2010 is sufficient and
198
+ # needs no decoding.
199
+ def escape_comment_key(value)
200
+ string(value).gsub('-', '‐')
201
+ end
202
+
187
203
  # Map a JS-boolean-shaped value to the JS `String(bool)` form. See
188
204
  # BarefootJS.pm's `bool_str` docstring for the boolean-only contract --
189
205
  # callers must have already classified the expression as boolean-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/erb",
3
- "version": "0.33.6",
3
+ "version": "0.35.0",
4
4
  "description": "ERB (Embedded Ruby) adapter for BarefootJS — compiles IR to .erb templates and ships the Ruby rendering backend; runs under any Rack app (Sinatra, Rails)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -54,7 +54,7 @@
54
54
  "directory": "packages/adapter-erb"
55
55
  },
56
56
  "dependencies": {
57
- "@barefootjs/shared": "0.33.6"
57
+ "@barefootjs/shared": "0.35.0"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "@barefootjs/jsx": ">=0.2.0",
@@ -71,9 +71,9 @@
71
71
  },
72
72
  "devDependencies": {
73
73
  "@barefootjs/adapter-tests": "0.1.0",
74
- "@barefootjs/jsx": "0.33.6",
75
- "@barefootjs/vite": "0.33.6",
76
- "@barefootjs/client": "0.33.6",
74
+ "@barefootjs/jsx": "0.35.0",
75
+ "@barefootjs/vite": "0.35.0",
76
+ "@barefootjs/client": "0.35.0",
77
77
  "typescript": "^5.0.0",
78
78
  "vite": "^6.0.0"
79
79
  }
@@ -18,7 +18,7 @@
18
18
  * rather than re-exposing the whole adapter.
19
19
  */
20
20
 
21
- import type { ParsedExpr, CompilerError, IRMetadata } from '@barefootjs/jsx'
21
+ import type { ParsedExpr, CompilerError, IRMetadata, LoweringMatcher } from '@barefootjs/jsx'
22
22
 
23
23
  export interface ErbEmitContext {
24
24
  /**
@@ -27,6 +27,17 @@ export interface ErbEmitContext {
27
27
  */
28
28
  readonly _searchParamsLocals: Set<string>
29
29
 
30
+ /**
31
+ * Registered lowering-plugin matchers (#2057), bound to this component's
32
+ * metadata at init. Read by `ErbTopLevelEmitter`'s `lowering` seam (#2843)
33
+ * so a registered call — the built-in `queryHref`, or any userland plugin —
34
+ * is recognised no matter where it sits in an expression tree (a ternary
35
+ * branch, a template-literal interpolation, …), not only when it's the call
36
+ * the adapter's own top-level conversion entry point
37
+ * (`convertExpressionToRuby`) is asked to lower directly.
38
+ */
39
+ readonly _loweringMatchers: readonly LoweringMatcher[]
40
+
30
41
  /**
31
42
  * Inline a module-scope pure string-literal const by name as the resolved
32
43
  * literal value, or null when the name is not such a const.
@@ -86,8 +86,6 @@ import {
86
86
  lookupStaticRecordLiteral,
87
87
  searchParamsLocalNames,
88
88
  prepareLoweringMatchers,
89
- queryHrefArgs,
90
- isValidHelperId,
91
89
  sortComparatorFromArrow,
92
90
  isDangerousInnerHtmlAttr,
93
91
  resolveDangerousInnerHtml,
@@ -417,7 +415,7 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
417
415
  */
418
416
  parseUndefinedAlternateTernary(
419
417
  expr: string,
420
- ): { condition: string; consequent: string } | null {
418
+ ): { condition: string; consequent: string; testParsed: ParsedExpr; consequentParsed: ParsedExpr } | null {
421
419
  const parsed = parseExpression(expr.trim())
422
420
  if (parsed?.kind !== 'conditional') return null
423
421
  const alt = parsed.alternate
@@ -425,13 +423,16 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
425
423
  (alt.kind === 'identifier' && (alt.name === 'undefined' || alt.name === 'null')) ||
426
424
  (alt.kind === 'literal' && (alt.value === null || alt.value === undefined))
427
425
  if (!isUndef) return null
428
- // Serialise the parsed sub-expressions back to JS source rather than
429
- // slicing `expr` text `indexOf('?')` / `lastIndexOf(':')` would
430
- // mis-split when the consequent itself contains `?` / `:` inside a
431
- // string or nested ternary (`cond ? 'a:b' : undefined`).
426
+ // `condition`/`consequent` are DEBUG text only (`exprToString` renders an
427
+ // unsupported nested shape like an object literal as non-reparseable
428
+ // `[UNSUPPORTED: …]` text) callers that need to re-lower the
429
+ // sub-expression must use `testParsed`/`consequentParsed` (the actual
430
+ // parsed trees) as `preParsed`, not re-parse these strings.
432
431
  return {
433
432
  condition: exprToString(parsed.test),
434
433
  consequent: exprToString(parsed.consequent),
434
+ testParsed: parsed.test,
435
+ consequentParsed: parsed.consequent,
435
436
  }
436
437
  }
437
438
 
@@ -1036,11 +1037,12 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
1036
1037
  // `<!--bf-loop-i:KEY-->` anchor before each item's (possibly empty)
1037
1038
  // conditional content so the client's `mapArrayAnchored` can hydrate
1038
1039
  // every SSR-rendered item by its anchor. `bf.comment` prepends `bf-`,
1039
- // so `"loop-i:" + KEY` yields `<!--bf-loop-i:KEY-->`. `bf.string` keeps
1040
- // the concatenation valid when KEY isn't already a Ruby String.
1040
+ // so `"loop-i:" + KEY` yields `<!--bf-loop-i:KEY-->`. `bf.escape_comment_key`
1041
+ // (#2795 follow-up) both stringifies KEY and neutralizes `-` so it can't
1042
+ // spell `-->` and close the comment early — see its doc in `barefoot_js.rb`.
1041
1043
  const children =
1042
1044
  loop.bodyIsItemConditional && loop.key
1043
- ? `<%= bf.comment("loop-i:" + bf.string(${this.convertExpressionToRuby(loop.key)})) %>\n${renderedChildren}`
1045
+ ? `<%= bf.comment("loop-i:" + bf.escape_comment_key(${this.convertExpressionToRuby(loop.key)})) %>\n${renderedChildren}`
1044
1046
  : renderedChildren
1045
1047
 
1046
1048
  const lines: string[] = []
@@ -1501,8 +1503,8 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
1501
1503
  {
1502
1504
  const m = this.parseUndefinedAlternateTernary(value.expr)
1503
1505
  if (m) {
1504
- const cond = this.convertExpressionToRuby(m.condition)
1505
- const val = this.convertExpressionToRuby(m.consequent)
1506
+ const cond = this.convertExpressionToRuby('', m.testParsed)
1507
+ const val = this.convertExpressionToRuby('', m.consequentParsed)
1506
1508
  return `<% if bf.truthy?(${cond}) %>${name}="<%= bf.h(${val}) %>"<% end %>`
1507
1509
  }
1508
1510
  }
@@ -1819,7 +1821,7 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
1819
1821
  const hasTaggedTemplate = /[A-Za-z_$][\w$]*\s*`/.test(probe)
1820
1822
  if (!startsAsObjectLiteral && !hasTaggedTemplate) return false
1821
1823
  const parsed = parseExpression(expr.trim())
1822
- const support = isSupported(parsed)
1824
+ const support = isSupported(parsed, { loweringMatchers: this._loweringMatchers })
1823
1825
  if (parsed.kind !== 'unsupported' && support.supported) return false
1824
1826
  const reason = support.reason ?? (parsed.kind === 'unsupported' ? parsed.reason : undefined)
1825
1827
  const reasonLine = reason ? `\n${reason}` : ''
@@ -1846,6 +1848,7 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
1846
1848
  private get emitCtx(): ErbEmitContext {
1847
1849
  return {
1848
1850
  _searchParamsLocals: this._searchParamsLocals,
1851
+ _loweringMatchers: this._loweringMatchers,
1849
1852
  resolveModuleStringConst: (name) => this.resolveModuleStringConst(name),
1850
1853
  resolveLiteralConst: (name) => this.resolveLiteralConst(name),
1851
1854
  resolveStaticRecordLiteral: (o, k) => this.resolveStaticRecordLiteral(o, k),
@@ -1902,42 +1905,16 @@ export class ErbAdapter extends BaseAdapter implements IRNodeEmitter<ErbRenderCt
1902
1905
  parsed = parseExpression(trimmed)
1903
1906
  }
1904
1907
 
1905
- // Registered call lowerings, including the built-in `queryHref` plugin,
1906
- // which lowers `queryHref(base, { … })` to a neutral `guard-list` on
1907
- // the `query` helper → `bf.query(base, <triples>)`. Recognised before
1908
- // the support gate because the object-literal arg is otherwise
1909
- // `unsupported` (BF101). The `bf.query` helper includes a pair iff its
1910
- // guard is truthy AND its value is a non-empty string (the client's
1911
- // `if (value)`): a plain `key: v` passes guard `true`, a conditional
1912
- // `key: cond ? v : undefined` passes the lowered cond. Only the
1913
- // `query` helper renders to `bf.query`; another guard-list helper must
1914
- // not be silently mis-rendered as a query.
1915
- if (parsed.kind === 'call') {
1916
- for (const matcher of this._loweringMatchers) {
1917
- const node = matcher(parsed.callee, parsed.args)
1918
- if (node?.kind === 'guard-list' && node.helper === 'query') {
1919
- const argsRuby = queryHrefArgs(node, n => this.renderParsedExprToRuby(n))
1920
- return `bf.query(${argsRuby.join(', ')})`
1921
- }
1922
- // Generic `helper-call` (#2069) — the neutral vocabulary's escape
1923
- // hatch for a userland `LoweringPlugin` that lowers to a single
1924
- // runtime-helper invocation. `bf.<helper>(args…)` mirrors the
1925
- // `query` helper's own naming convention exactly: the framework
1926
- // renders the call, the plugin author registers `<helper>` as a
1927
- // Ruby-callable method on their own `bf` helper object — same
1928
- // contract as `bf.query` itself, just not built in.
1929
- if (node?.kind === 'helper-call' && isValidHelperId(node.helper)) {
1930
- const argsX = node.args.map(a => this.renderParsedExprToRuby(a))
1931
- return `bf.${node.helper}(${argsX.join(', ')})`
1932
- }
1933
- }
1934
- }
1935
-
1936
1908
  // `pos` distinguishes a derived-seed RHS (an assignment, checked via
1937
1909
  // `isSupportedValue`) from every other, genuinely rendered call site —
1938
1910
  // the rendered gate would otherwise re-refuse a tree the seed plan
1939
1911
  // already classified `derived` at value position (#2696 review).
1940
- const support = pos === 'value' ? isSupportedValue(parsed) : isSupported(parsed)
1912
+ // Registered call lowerings (#2057) including the built-in `queryHref`
1913
+ // plugin — are consulted by this gate no matter where the call sits in
1914
+ // the tree (`ErbTopLevelEmitter`'s `lowering` seam does the actual
1915
+ // rendering below, via `emitParsedExpr`'s shared `call` case).
1916
+ const supportOpts = { loweringMatchers: this._loweringMatchers }
1917
+ const support = pos === 'value' ? isSupportedValue(parsed, supportOpts) : isSupported(parsed, supportOpts)
1941
1918
  if (!support.supported) {
1942
1919
  this.errors.push({
1943
1920
  code: 'BF101',
@@ -46,6 +46,10 @@ import {
46
46
  identifierPath,
47
47
  matchSearchParamsMethodCall,
48
48
  sortComparatorFromArrow,
49
+ type LoweringEmitter,
50
+ type LoweringNode,
51
+ queryHrefArgs,
52
+ isValidHelperId,
49
53
  } from '@barefootjs/jsx'
50
54
 
51
55
  import type { ErbEmitContext } from '../emit-context.ts'
@@ -346,6 +350,35 @@ export class ErbTopLevelEmitter implements ParsedExprEmitter {
346
350
  this.ctx = ctx
347
351
  }
348
352
 
353
+ /**
354
+ * Registered-lowering seam (#2843): `emitParsedExpr`'s shared `call` case
355
+ * tries every matcher here BEFORE `call()` itself, so a registered call
356
+ * (the built-in `queryHref`, or any userland plugin) is recognised no
357
+ * matter where it sits in the tree. `render` is what used to live inline
358
+ * in `ErbAdapter.convertExpressionToRuby` before the object-literal
359
+ * support-gate refusal (`checkSupport`'s `call` arm, now itself
360
+ * registry-aware) made the pre-gate special case unnecessary.
361
+ */
362
+ get lowering(): LoweringEmitter {
363
+ return {
364
+ matchers: this.ctx._loweringMatchers,
365
+ render: (node: LoweringNode, emit: (e: ParsedExpr) => string): string | null => {
366
+ // `query` guard-list — `queryHref`-shaped.
367
+ if (node.kind === 'guard-list' && node.helper === 'query') {
368
+ const qArgs = queryHrefArgs(node, emit)
369
+ return `bf.query(${qArgs.join(', ')})`
370
+ }
371
+ // Generic `helper-call` (#2069) — a userland `LoweringPlugin`'s
372
+ // single runtime-helper invocation; `bf.<helper>(args…)` mirrors
373
+ // the `query` helper's own naming convention.
374
+ if (node.kind === 'helper-call' && isValidHelperId(node.helper)) {
375
+ return `bf.${node.helper}(${node.args.map(emit).join(', ')})`
376
+ }
377
+ return null
378
+ },
379
+ }
380
+ }
381
+
349
382
  identifier(name: string): string {
350
383
  // `undefined` / `null` nested inside a larger expression tree (e.g.
351
384
  // `props.isActive ? 'page' : undefined`) — the top-level short-circuits
@@ -10,6 +10,10 @@
10
10
  import type { ConformancePins } from '@barefootjs/jsx'
11
11
 
12
12
  export const conformancePins: ConformancePins = {
13
+ // #2843: graduated — a registered lowering call inside a ternary
14
+ // attribute branch (or any nested value position) is now recognised via
15
+ // `ErbTopLevelEmitter`'s `lowering` seam + the registry-aware support
16
+ // gate, matching the direct-call attribute path exactly.
13
17
  'filter-typeof-predicate': [{ code: 'BF021', severity: 'error' }],
14
18
  'map-array-builder-body': [{ code: 'BF021', severity: 'error' }],
15
19
  'map-array-builder-escaping': [{ code: 'BF021', severity: 'error' }],
@@ -15,7 +15,4 @@ import type { RenderDivergences } from '@barefootjs/jsx'
15
15
  // at value position and the runtime evaluator's `object-literal` case
16
16
  // now merges it, so the seed classifies `derived` and SSRs identically
17
17
  // to Hono.
18
- export const renderDivergences: RenderDivergences = {
19
- 'aliased-loop-source':
20
- 'A `.map()` loop whose source is a local const alias of a signal getter (`const items__alias = items`) throws `undefined method \'length\' for nil (NoMethodError)` on real Ruby ERB — the seeded loop data is keyed by the signal\'s real name (`items`), and the alias hop is never resolved when deciding what to seed under `items__alias`. This is the SSR-side twin of #2778 (fixed for the CSR client-JS template in the same PR that added this fixture) — that fix only touches client-JS emission, not SSR data-seeding. Tracked at https://github.com/piconic-ai/barefootjs/issues/2813; graduate by resolving the alias hop at SSR-seeding time using the same `resolveAliasOrigin`/`resolveGetterAliases` mechanism #2778 introduced, rather than a third alias-hop walker.',
21
- }
18
+ export const renderDivergences: RenderDivergences = {}
@@ -618,6 +618,20 @@ function buildRubyProps(
618
618
  }
619
619
  }
620
620
 
621
+ // #2813: `extractSsrDefaults` can seed an entry under a name that is
622
+ // none of `propsParams` / `signals` / `memos` above — a bare local-const
623
+ // alias of a signal/memo getter (`const items__alias = items`,
624
+ // `ssr-defaults.ts`) seeds `items__alias` alongside `items`. Production
625
+ // hands the whole manifest-derived stash straight through
626
+ // (`deriveStashFromDefaults` IS the production seeding mechanism here,
627
+ // not a harness stand-in), so any leftover `derivedProps` key must be
628
+ // copied into `obj` too, or this harness would silently omit what
629
+ // production actually provides, mismeasuring this conformance case.
630
+ for (const [name, value] of Object.entries(derivedProps)) {
631
+ if (Object.prototype.hasOwnProperty.call(obj, name)) continue
632
+ obj[name] = value
633
+ }
634
+
621
635
  const needsSearchParams = importsSearchParams(ir.metadata)
622
636
 
623
637
  return { obj, needsSearchParams, userProps }