@barefootjs/jinja 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.
@@ -18,13 +18,23 @@
18
18
  * genuinely needs it, so the seam documents the real cross-module coupling
19
19
  * rather than re-exposing the whole adapter.
20
20
  */
21
- import type { ParsedExpr, CompilerError, IRMetadata } from '@barefootjs/jsx';
21
+ import type { ParsedExpr, CompilerError, IRMetadata, LoweringMatcher } from '@barefootjs/jsx';
22
22
  export interface JinjaEmitContext {
23
23
  /**
24
24
  * (#1922) Local binding names the request-scoped `searchParams()` env signal
25
25
  * is imported under. Non-empty enables the env-signal method-call lowering.
26
26
  */
27
27
  readonly _searchParamsLocals: Set<string>;
28
+ /**
29
+ * Registered lowering-plugin matchers (#2057), bound to this component's
30
+ * metadata at init. Read by `JinjaTopLevelEmitter`'s `lowering` seam
31
+ * (#2843) so a registered call — the built-in `queryHref`, or any
32
+ * userland plugin — is recognised no matter where it sits in an
33
+ * expression tree (a ternary branch, a template-literal interpolation, …),
34
+ * not only when it's the call the adapter's own top-level conversion
35
+ * entry point (`convertExpressionToJinja`) is asked to lower directly.
36
+ */
37
+ readonly _loweringMatchers: readonly LoweringMatcher[];
28
38
  /**
29
39
  * Inline a module-scope pure string-literal const by name as the resolved
30
40
  * 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;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAE5E,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAEzC;;;OAGG;IACH,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAEtD,oFAAoF;IACpF,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAEjD;;;OAGG;IACH,2BAA2B,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAE3E,wDAAwD;IACxD,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAExD,yEAAyE;IACzE,4BAA4B,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;CACtE;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,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;;;;;;;;OAQG;IACH,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;IAEtE;;;;;;OAMG;IACH,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;CACtE;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,MAAM,CAAA;IAElG;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,CAAA;CACjC"}
1
+ {"version":3,"file":"emit-context.d.ts","sourceRoot":"","sources":["../../src/adapter/emit-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAE7F,MAAM,WAAW,gBAAgB;IAC/B;;;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,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAEtD,oFAAoF;IACpF,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAEjD;;;OAGG;IACH,2BAA2B,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAE3E,wDAAwD;IACxD,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAExD,yEAAyE;IACzE,4BAA4B,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;CACtE;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,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;;;;;;;;OAQG;IACH,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;IAEtE;;;;;;OAMG;IACH,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;CACtE;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;OAMG;IACH,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,MAAM,CAAA;IAElG;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,CAAA;CACjC"}
@@ -47,7 +47,7 @@
47
47
  * unaffected and still used for that path plus the filter-predicate
48
48
  * entry point `_renderJinjaFilterExprPublic`.
49
49
  */
50
- import { type ParsedExprEmitter, type ArrayMethod, type LiteralType, type ParsedExpr, type ObjectLiteralProperty, type FlatDepth, type TemplatePart } from '@barefootjs/jsx';
50
+ import { type ParsedExprEmitter, type LoweringEmitter, type ArrayMethod, type LiteralType, type ParsedExpr, type ObjectLiteralProperty, type FlatDepth, type TemplatePart } from '@barefootjs/jsx';
51
51
  import type { JinjaEmitContext } from '../emit-context.ts';
52
52
  /**
53
53
  * Route a condition-TEST position through `bf.truthy(...)` unless the node
@@ -110,6 +110,16 @@ export declare class JinjaFilterEmitter implements ParsedExprEmitter {
110
110
  export declare class JinjaTopLevelEmitter implements ParsedExprEmitter {
111
111
  private readonly ctx;
112
112
  constructor(ctx: JinjaEmitContext);
113
+ /**
114
+ * Registered-lowering seam (#2843): `emitParsedExpr`'s shared `call` case
115
+ * tries every matcher here BEFORE `call()` itself, so a registered call
116
+ * (the built-in `queryHref`, or any userland plugin) is recognised no
117
+ * matter where it sits in the tree. `render` is what used to live inline
118
+ * in `JinjaAdapter.convertExpressionToJinja` before the object-literal
119
+ * support-gate refusal (`checkSupport`'s `call` arm, now itself
120
+ * registry-aware) made the pre-gate special case unnecessary.
121
+ */
122
+ get lowering(): LoweringEmitter;
113
123
  identifier(name: string): string;
114
124
  literal(value: string | number | boolean | null, literalType: LiteralType): string;
115
125
  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":"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"}
@@ -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,
@@ -187691,7 +187689,9 @@ import {
187691
187689
  groupObjectLiteralSegments,
187692
187690
  identifierPath,
187693
187691
  matchSearchParamsMethodCall,
187694
- sortComparatorFromArrow
187692
+ sortComparatorFromArrow,
187693
+ queryHrefArgs,
187694
+ isValidHelperId
187695
187695
  } from "@barefootjs/jsx";
187696
187696
  var PREDICATE_METHODS = new Set([
187697
187697
  "filter",
@@ -187820,6 +187820,21 @@ class JinjaTopLevelEmitter {
187820
187820
  constructor(ctx) {
187821
187821
  this.ctx = ctx;
187822
187822
  }
187823
+ get lowering() {
187824
+ return {
187825
+ matchers: this.ctx._loweringMatchers,
187826
+ render: (node, emit) => {
187827
+ if (node.kind === "guard-list" && node.helper === "query") {
187828
+ const qArgs = queryHrefArgs(node, emit);
187829
+ return `bf.query(${qArgs.join(", ")})`;
187830
+ }
187831
+ if (node.kind === "helper-call" && isValidHelperId(node.helper)) {
187832
+ return `bf.${node.helper}(${node.args.map(emit).join(", ")})`;
187833
+ }
187834
+ return null;
187835
+ }
187836
+ };
187837
+ }
187823
187838
  identifier(name) {
187824
187839
  if (name === "undefined" || name === "null")
187825
187840
  return "none";
@@ -188677,7 +188692,7 @@ ${whenTrue}
188677
188692
  const preambleLines = (loop.preamble?.declarations ?? []).map((d) => `{% set ${jinjaIdent(d.name)} = ${this.convertExpressionToJinja(d.raw, d.valueParsed)} %}`);
188678
188693
  const childrenUnderLoop = this.renderChildren(loop.children);
188679
188694
  this.inLoop = prevInLoop;
188680
- const bodyChildren = loop.bodyIsItemConditional && loop.key ? `{{ bf.comment("loop-i:" ~ bf.string(${this.convertExpressionToJinja(loop.key)})) | safe }}
188695
+ const bodyChildren = loop.bodyIsItemConditional && loop.key ? `{{ bf.comment("loop-i:" ~ bf.escape_comment_key(${this.convertExpressionToJinja(loop.key)})) | safe }}
188681
188696
  ${childrenUnderLoop}` : childrenUnderLoop;
188682
188697
  this.scope = prevScope;
188683
188698
  const lines = [];
@@ -188882,8 +188897,8 @@ ${body}
188882
188897
  {
188883
188898
  const m = this.parseUndefinedAlternateTernary(value.expr);
188884
188899
  if (m) {
188885
- const cond = this.convertConditionToJinja(m.condition);
188886
- const val = this.convertExpressionToJinja(m.consequent);
188900
+ const cond = this.convertConditionToJinja(m.condition, m.testParsed);
188901
+ const val = this.convertExpressionToJinja(m.consequent, m.consequentParsed);
188887
188902
  return `
188888
188903
  {% if ${cond} %}
188889
188904
  ${name}="{{ bf.string(${val}) }}"
@@ -189016,7 +189031,7 @@ ${name}="{{ bf.string(${val}) }}"
189016
189031
  if (!startsAsObjectLiteral && !hasTaggedTemplate)
189017
189032
  return false;
189018
189033
  const parsed = parseExpression2(expr.trim());
189019
- const support = isSupported(parsed);
189034
+ const support = isSupported(parsed, { loweringMatchers: this._loweringMatchers });
189020
189035
  if (parsed.kind !== "unsupported" && support.supported)
189021
189036
  return false;
189022
189037
  const reason = support.reason ?? (parsed.kind === "unsupported" ? parsed.reason : undefined);
@@ -189036,6 +189051,7 @@ ${reason}` : "";
189036
189051
  get emitCtx() {
189037
189052
  return {
189038
189053
  _searchParamsLocals: this._searchParamsLocals,
189054
+ _loweringMatchers: this._loweringMatchers,
189039
189055
  _resolveModuleStringConst: (name) => this._resolveModuleStringConst(name),
189040
189056
  _resolveLiteralConst: (name) => this._resolveLiteralConst(name),
189041
189057
  _resolveStaticRecordLiteral: (o, k) => this._resolveStaticRecordLiteral(o, k),
@@ -189069,20 +189085,8 @@ ${reason}` : "";
189069
189085
  return "''";
189070
189086
  parsed = parseExpression2(trimmed);
189071
189087
  }
189072
- if (parsed.kind === "call") {
189073
- for (const matcher of this._loweringMatchers) {
189074
- const node = matcher(parsed.callee, parsed.args);
189075
- if (node?.kind === "guard-list" && node.helper === "query") {
189076
- const qArgs = queryHrefArgs(node, (n) => this.renderParsedExprToJinja(n));
189077
- return `bf.query(${qArgs.join(", ")})`;
189078
- }
189079
- if (node?.kind === "helper-call" && isValidHelperId(node.helper)) {
189080
- const argsX = node.args.map((a) => this.renderParsedExprToJinja(a));
189081
- return `bf.${node.helper}(${argsX.join(", ")})`;
189082
- }
189083
- }
189084
- }
189085
- const support = pos === "value" ? isSupportedValue(parsed) : isSupported(parsed);
189088
+ const supportOpts = { loweringMatchers: this._loweringMatchers };
189089
+ const support = pos === "value" ? isSupportedValue(parsed, supportOpts) : isSupported(parsed, supportOpts);
189086
189090
  if (!support.supported) {
189087
189091
  this.errors.push({
189088
189092
  code: "BF101",
@@ -189127,7 +189131,9 @@ Options:
189127
189131
  return null;
189128
189132
  return {
189129
189133
  condition: exprToString(parsed.test),
189130
- consequent: exprToString(parsed.consequent)
189134
+ consequent: exprToString(parsed.consequent),
189135
+ testParsed: parsed.test,
189136
+ consequentParsed: parsed.consequent
189131
189137
  };
189132
189138
  }
189133
189139
  isBooleanTypedPropRef(expr) {
@@ -89,6 +89,7 @@
89
89
  */
90
90
  import type { ComponentIR, IRNode, IRElement, IRText, IRExpression, IRConditional, IRLoop, IRComponent, IRFragment, IRSlot, IRIfStatement, IRProvider, IRAsync, TemplatePrimitiveRegistry } from '@barefootjs/jsx';
91
91
  import { BaseAdapter, type AdapterOutput, type AdapterGenerateOptions, type IRNodeEmitter, type EmitIRNode } from '@barefootjs/jsx';
92
+ import type { ParsedExpr } from '@barefootjs/jsx';
92
93
  import type { JinjaRenderCtx } from './lib/types.ts';
93
94
  export type { JinjaAdapterOptions } from './lib/types.ts';
94
95
  import type { JinjaAdapterOptions } from './lib/types.ts';
@@ -390,10 +391,23 @@ export declare class JinjaAdapter extends BaseAdapter implements IRNodeEmitter<J
390
391
  * Parse `cond ? value : undefined` (or `: null`), returning the
391
392
  * condition/consequent source spans, else `null`. Used for the
392
393
  * attribute-omission rule (#1897).
394
+ *
395
+ * `testParsed`/`consequentParsed` (#2843 review) are the ACTUAL parsed
396
+ * sub-trees, for the caller to pass through as `preParsed` — `condition`/
397
+ * `consequent` are `exprToString` output, a DEBUG formatter that renders
398
+ * any nested `object-literal` as the non-reparseable `[UNSUPPORTED: …]`
399
+ * placeholder (unlike `stringifyParsedExpr`, meant for round-tripping).
400
+ * Re-parsing THAT string for a consequent like `queryHref(base, { tag })`
401
+ * would corrupt the call's args before the lowering registry — now
402
+ * consulted at any nesting depth (#2843) — ever sees them. Kept for any
403
+ * caller that only needs the string form; the parsed trees are the
404
+ * correct input wherever the caller can accept `preParsed`.
393
405
  */
394
406
  parseUndefinedAlternateTernary(expr: string): {
395
407
  condition: string;
396
408
  consequent: string;
409
+ testParsed: ParsedExpr;
410
+ consequentParsed: ParsedExpr;
397
411
  } | null;
398
412
  isBooleanTypedPropRef(expr: string): boolean;
399
413
  /** Position-accurate loop-bound-name check — see `this.scope`'s docstring. */
@@ -1 +1 @@
1
- {"version":3,"file":"jinja-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/jinja-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"jinja-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/jinja-adapter.ts"],"names":[],"mappings":"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"}
@@ -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,eA6D7B,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,eAiE7B,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,
@@ -187691,7 +187689,9 @@ import {
187691
187689
  groupObjectLiteralSegments,
187692
187690
  identifierPath,
187693
187691
  matchSearchParamsMethodCall,
187694
- sortComparatorFromArrow
187692
+ sortComparatorFromArrow,
187693
+ queryHrefArgs,
187694
+ isValidHelperId
187695
187695
  } from "@barefootjs/jsx";
187696
187696
  var PREDICATE_METHODS = new Set([
187697
187697
  "filter",
@@ -187820,6 +187820,21 @@ class JinjaTopLevelEmitter {
187820
187820
  constructor(ctx) {
187821
187821
  this.ctx = ctx;
187822
187822
  }
187823
+ get lowering() {
187824
+ return {
187825
+ matchers: this.ctx._loweringMatchers,
187826
+ render: (node, emit) => {
187827
+ if (node.kind === "guard-list" && node.helper === "query") {
187828
+ const qArgs = queryHrefArgs(node, emit);
187829
+ return `bf.query(${qArgs.join(", ")})`;
187830
+ }
187831
+ if (node.kind === "helper-call" && isValidHelperId(node.helper)) {
187832
+ return `bf.${node.helper}(${node.args.map(emit).join(", ")})`;
187833
+ }
187834
+ return null;
187835
+ }
187836
+ };
187837
+ }
187823
187838
  identifier(name) {
187824
187839
  if (name === "undefined" || name === "null")
187825
187840
  return "none";
@@ -188677,7 +188692,7 @@ ${whenTrue}
188677
188692
  const preambleLines = (loop.preamble?.declarations ?? []).map((d) => `{% set ${jinjaIdent(d.name)} = ${this.convertExpressionToJinja(d.raw, d.valueParsed)} %}`);
188678
188693
  const childrenUnderLoop = this.renderChildren(loop.children);
188679
188694
  this.inLoop = prevInLoop;
188680
- const bodyChildren = loop.bodyIsItemConditional && loop.key ? `{{ bf.comment("loop-i:" ~ bf.string(${this.convertExpressionToJinja(loop.key)})) | safe }}
188695
+ const bodyChildren = loop.bodyIsItemConditional && loop.key ? `{{ bf.comment("loop-i:" ~ bf.escape_comment_key(${this.convertExpressionToJinja(loop.key)})) | safe }}
188681
188696
  ${childrenUnderLoop}` : childrenUnderLoop;
188682
188697
  this.scope = prevScope;
188683
188698
  const lines = [];
@@ -188882,8 +188897,8 @@ ${body}
188882
188897
  {
188883
188898
  const m = this.parseUndefinedAlternateTernary(value.expr);
188884
188899
  if (m) {
188885
- const cond = this.convertConditionToJinja(m.condition);
188886
- const val = this.convertExpressionToJinja(m.consequent);
188900
+ const cond = this.convertConditionToJinja(m.condition, m.testParsed);
188901
+ const val = this.convertExpressionToJinja(m.consequent, m.consequentParsed);
188887
188902
  return `
188888
188903
  {% if ${cond} %}
188889
188904
  ${name}="{{ bf.string(${val}) }}"
@@ -189016,7 +189031,7 @@ ${name}="{{ bf.string(${val}) }}"
189016
189031
  if (!startsAsObjectLiteral && !hasTaggedTemplate)
189017
189032
  return false;
189018
189033
  const parsed = parseExpression2(expr.trim());
189019
- const support = isSupported(parsed);
189034
+ const support = isSupported(parsed, { loweringMatchers: this._loweringMatchers });
189020
189035
  if (parsed.kind !== "unsupported" && support.supported)
189021
189036
  return false;
189022
189037
  const reason = support.reason ?? (parsed.kind === "unsupported" ? parsed.reason : undefined);
@@ -189036,6 +189051,7 @@ ${reason}` : "";
189036
189051
  get emitCtx() {
189037
189052
  return {
189038
189053
  _searchParamsLocals: this._searchParamsLocals,
189054
+ _loweringMatchers: this._loweringMatchers,
189039
189055
  _resolveModuleStringConst: (name) => this._resolveModuleStringConst(name),
189040
189056
  _resolveLiteralConst: (name) => this._resolveLiteralConst(name),
189041
189057
  _resolveStaticRecordLiteral: (o, k) => this._resolveStaticRecordLiteral(o, k),
@@ -189069,20 +189085,8 @@ ${reason}` : "";
189069
189085
  return "''";
189070
189086
  parsed = parseExpression2(trimmed);
189071
189087
  }
189072
- if (parsed.kind === "call") {
189073
- for (const matcher of this._loweringMatchers) {
189074
- const node = matcher(parsed.callee, parsed.args);
189075
- if (node?.kind === "guard-list" && node.helper === "query") {
189076
- const qArgs = queryHrefArgs(node, (n) => this.renderParsedExprToJinja(n));
189077
- return `bf.query(${qArgs.join(", ")})`;
189078
- }
189079
- if (node?.kind === "helper-call" && isValidHelperId(node.helper)) {
189080
- const argsX = node.args.map((a) => this.renderParsedExprToJinja(a));
189081
- return `bf.${node.helper}(${argsX.join(", ")})`;
189082
- }
189083
- }
189084
- }
189085
- const support = pos === "value" ? isSupportedValue(parsed) : isSupported(parsed);
189088
+ const supportOpts = { loweringMatchers: this._loweringMatchers };
189089
+ const support = pos === "value" ? isSupportedValue(parsed, supportOpts) : isSupported(parsed, supportOpts);
189086
189090
  if (!support.supported) {
189087
189091
  this.errors.push({
189088
189092
  code: "BF101",
@@ -189127,7 +189131,9 @@ Options:
189127
189131
  return null;
189128
189132
  return {
189129
189133
  condition: exprToString(parsed.test),
189130
- consequent: exprToString(parsed.consequent)
189134
+ consequent: exprToString(parsed.consequent),
189135
+ testParsed: parsed.test,
189136
+ consequentParsed: parsed.consequent
189131
189137
  };
189132
189138
  }
189133
189139
  isBooleanTypedPropRef(expr) {
@@ -189238,9 +189244,7 @@ var conformancePins = {
189238
189244
  "namespace-import-primitive": [{ code: "BF013", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2771" }]
189239
189245
  };
189240
189246
  // src/render-divergences.ts
189241
- var renderDivergences = {
189242
- "aliased-loop-source": "A `.map()` loop whose source is a local const alias of a signal getter (`const items__alias = items`) SSRs an empty `<ul>` on real Jinja2 — 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."
189243
- };
189247
+ var renderDivergences = {};
189244
189248
  export {
189245
189249
  renderDivergences,
189246
189250
  jinjaAdapter,
@@ -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
  }
@@ -2280,6 +2326,12 @@ class BindingScope {
2280
2326
  }
2281
2327
  }
2282
2328
 
2329
+ // ../jsx/src/ir-to-client-js/safe-html.ts
2330
+ function safeHtml(expr) {
2331
+ return expr;
2332
+ }
2333
+ var EMPTY_MARKUP = safeHtml("''");
2334
+
2283
2335
  // ../jsx/src/ir-to-client-js/html-template.ts
2284
2336
  var VOID_ELEMENTS = new Set([
2285
2337
  "area",
@@ -2566,6 +2618,7 @@ var CLIENT_EXPORTS = new Set([
2566
2618
  "isSSRPortal",
2567
2619
  "findSiblingSlot",
2568
2620
  "cleanupPortalPlaceholder",
2621
+ "trackPosition",
2569
2622
  "createSearchParams",
2570
2623
  "queryHref",
2571
2624
  "formatDate",
@@ -2626,7 +2679,8 @@ var BROWSER_ONLY_CLIENT_APIS = new Set([
2626
2679
  "createPortal",
2627
2680
  "isSSRPortal",
2628
2681
  "findSiblingSlot",
2629
- "cleanupPortalPlaceholder"
2682
+ "cleanupPortalPlaceholder",
2683
+ "trackPosition"
2630
2684
  ]);
2631
2685
  var REACTIVE_PRIMITIVES = new Set([
2632
2686
  "createSignal",
@@ -3225,30 +3279,6 @@ import ts16 from "typescript";
3225
3279
 
3226
3280
  // ../jsx/src/relocate.ts
3227
3281
  import ts18 from "typescript";
3228
-
3229
- // ../jsx/src/lowering-registry.ts
3230
- var plugins = [];
3231
- function registerLoweringPlugin(plugin) {
3232
- const existing = plugins.findIndex((p) => p.name === plugin.name);
3233
- if (existing >= 0)
3234
- plugins[existing] = plugin;
3235
- else
3236
- plugins.push(plugin);
3237
- }
3238
- function prepareLoweringMatchers(metadata) {
3239
- const matchers = [];
3240
- for (const plugin of plugins) {
3241
- const matcher = plugin.prepare(metadata);
3242
- if (matcher)
3243
- matchers.push(matcher);
3244
- }
3245
- return matchers;
3246
- }
3247
- function isValidHelperId(helper) {
3248
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(helper);
3249
- }
3250
-
3251
- // ../jsx/src/relocate.ts
3252
3282
  var REGISTRY_SAFE_BINDING_KINDS = new Set([
3253
3283
  "global",
3254
3284
  "module-import",
@@ -4715,6 +4745,17 @@ function emitParsedExpr(expr, emitter) {
4715
4745
  case "literal":
4716
4746
  return emitter.literal(expr.value, expr.literalType);
4717
4747
  case "call": {
4748
+ if (emitter.lowering) {
4749
+ for (const matcher of emitter.lowering.matchers) {
4750
+ const node = matcher(expr.callee, expr.args);
4751
+ if (!node)
4752
+ continue;
4753
+ const rendered = emitter.lowering.render(node, emit);
4754
+ if (rendered !== null)
4755
+ return rendered;
4756
+ break;
4757
+ }
4758
+ }
4718
4759
  const cb = asCallbackMethodCall(expr);
4719
4760
  if (cb)
4720
4761
  return emitter.callbackMethod(cb.method, cb.object, cb.arrow, cb.args, emit);
@@ -5723,6 +5764,21 @@ class JinjaTopLevelEmitter {
5723
5764
  constructor(ctx) {
5724
5765
  this.ctx = ctx;
5725
5766
  }
5767
+ get lowering() {
5768
+ return {
5769
+ matchers: this.ctx._loweringMatchers,
5770
+ render: (node, emit) => {
5771
+ if (node.kind === "guard-list" && node.helper === "query") {
5772
+ const qArgs = queryHrefArgs(node, emit);
5773
+ return `bf.query(${qArgs.join(", ")})`;
5774
+ }
5775
+ if (node.kind === "helper-call" && isValidHelperId(node.helper)) {
5776
+ return `bf.${node.helper}(${node.args.map(emit).join(", ")})`;
5777
+ }
5778
+ return null;
5779
+ }
5780
+ };
5781
+ }
5726
5782
  identifier(name) {
5727
5783
  if (name === "undefined" || name === "null")
5728
5784
  return "none";
@@ -6571,7 +6627,7 @@ ${whenTrue}
6571
6627
  const preambleLines = (loop.preamble?.declarations ?? []).map((d) => `{% set ${jinjaIdent(d.name)} = ${this.convertExpressionToJinja(d.raw, d.valueParsed)} %}`);
6572
6628
  const childrenUnderLoop = this.renderChildren(loop.children);
6573
6629
  this.inLoop = prevInLoop;
6574
- const bodyChildren = loop.bodyIsItemConditional && loop.key ? `{{ bf.comment("loop-i:" ~ bf.string(${this.convertExpressionToJinja(loop.key)})) | safe }}
6630
+ const bodyChildren = loop.bodyIsItemConditional && loop.key ? `{{ bf.comment("loop-i:" ~ bf.escape_comment_key(${this.convertExpressionToJinja(loop.key)})) | safe }}
6575
6631
  ${childrenUnderLoop}` : childrenUnderLoop;
6576
6632
  this.scope = prevScope;
6577
6633
  const lines = [];
@@ -6776,8 +6832,8 @@ ${body}
6776
6832
  {
6777
6833
  const m = this.parseUndefinedAlternateTernary(value.expr);
6778
6834
  if (m) {
6779
- const cond = this.convertConditionToJinja(m.condition);
6780
- const val = this.convertExpressionToJinja(m.consequent);
6835
+ const cond = this.convertConditionToJinja(m.condition, m.testParsed);
6836
+ const val = this.convertExpressionToJinja(m.consequent, m.consequentParsed);
6781
6837
  return `
6782
6838
  {% if ${cond} %}
6783
6839
  ${name}="{{ bf.string(${val}) }}"
@@ -6910,7 +6966,7 @@ ${name}="{{ bf.string(${val}) }}"
6910
6966
  if (!startsAsObjectLiteral && !hasTaggedTemplate)
6911
6967
  return false;
6912
6968
  const parsed = parseExpression(expr.trim());
6913
- const support = isSupported(parsed);
6969
+ const support = isSupported(parsed, { loweringMatchers: this._loweringMatchers });
6914
6970
  if (parsed.kind !== "unsupported" && support.supported)
6915
6971
  return false;
6916
6972
  const reason = support.reason ?? (parsed.kind === "unsupported" ? parsed.reason : undefined);
@@ -6930,6 +6986,7 @@ ${reason}` : "";
6930
6986
  get emitCtx() {
6931
6987
  return {
6932
6988
  _searchParamsLocals: this._searchParamsLocals,
6989
+ _loweringMatchers: this._loweringMatchers,
6933
6990
  _resolveModuleStringConst: (name) => this._resolveModuleStringConst(name),
6934
6991
  _resolveLiteralConst: (name) => this._resolveLiteralConst(name),
6935
6992
  _resolveStaticRecordLiteral: (o, k) => this._resolveStaticRecordLiteral(o, k),
@@ -6963,20 +7020,8 @@ ${reason}` : "";
6963
7020
  return "''";
6964
7021
  parsed = parseExpression(trimmed);
6965
7022
  }
6966
- if (parsed.kind === "call") {
6967
- for (const matcher of this._loweringMatchers) {
6968
- const node = matcher(parsed.callee, parsed.args);
6969
- if (node?.kind === "guard-list" && node.helper === "query") {
6970
- const qArgs = queryHrefArgs(node, (n) => this.renderParsedExprToJinja(n));
6971
- return `bf.query(${qArgs.join(", ")})`;
6972
- }
6973
- if (node?.kind === "helper-call" && isValidHelperId(node.helper)) {
6974
- const argsX = node.args.map((a) => this.renderParsedExprToJinja(a));
6975
- return `bf.${node.helper}(${argsX.join(", ")})`;
6976
- }
6977
- }
6978
- }
6979
- const support = pos === "value" ? isSupportedValue(parsed) : isSupported(parsed);
7023
+ const supportOpts = { loweringMatchers: this._loweringMatchers };
7024
+ const support = pos === "value" ? isSupportedValue(parsed, supportOpts) : isSupported(parsed, supportOpts);
6980
7025
  if (!support.supported) {
6981
7026
  this.errors.push({
6982
7027
  code: "BF101",
@@ -7021,7 +7066,9 @@ Options:
7021
7066
  return null;
7022
7067
  return {
7023
7068
  condition: exprToString(parsed.test),
7024
- consequent: exprToString(parsed.consequent)
7069
+ consequent: exprToString(parsed.consequent),
7070
+ testParsed: parsed.test,
7071
+ consequentParsed: parsed.consequent
7025
7072
  };
7026
7073
  }
7027
7074
  isBooleanTypedPropRef(expr) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/jinja",
3
- "version": "0.33.6",
3
+ "version": "0.35.0",
4
4
  "description": "Jinja2 adapter for BarefootJS — compiles IR to .jinja templates and ships the Python BarefootJS rendering runtime; runs under any Python web framework (Flask, etc.)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -53,7 +53,7 @@
53
53
  "directory": "packages/adapter-jinja"
54
54
  },
55
55
  "dependencies": {
56
- "@barefootjs/shared": "0.33.6"
56
+ "@barefootjs/shared": "0.35.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@barefootjs/jsx": ">=0.2.0",
@@ -70,9 +70,9 @@
70
70
  },
71
71
  "devDependencies": {
72
72
  "@barefootjs/adapter-tests": "0.1.0",
73
- "@barefootjs/jsx": "0.33.6",
74
- "@barefootjs/vite": "0.33.6",
75
- "@barefootjs/client": "0.33.6",
73
+ "@barefootjs/jsx": "0.35.0",
74
+ "@barefootjs/vite": "0.35.0",
75
+ "@barefootjs/client": "0.35.0",
76
76
  "typescript": "^5.0.0",
77
77
  "vite": "^6.0.0"
78
78
  }
@@ -659,6 +659,22 @@ class BarefootJS:
659
659
  def comment(self, text: str) -> str:
660
660
  return f"<!--bf-{text}-->"
661
661
 
662
+ def escape_comment_key(self, value: Any) -> str:
663
+ """Neutralize a value for splicing into `comment`'s HTML comment
664
+ content (#2795 follow-up). `comment` itself does no escaping --
665
+ fine for every other caller (marker IDs like "cond-start:s0",
666
+ "loop:l0", ...), which are entirely compiler-generated, but the
667
+ whole-item-conditional loop's "loop-i:<key>" anchor carries a
668
+ user-controlled key. Standard HTML escaping doesn't help inside a
669
+ comment -- only the literal sequence "-->" terminates it early, and
670
+ &/</>/"/' are not special there. The key's exact text doesn't need
671
+ to round-trip (the client's mapArrayAnchored matches items
672
+ positionally and by its own JS-computed key, never by re-parsing
673
+ the anchor Comment.nodeValue), so replacing every "-" with the
674
+ visually-similar U+2010 is sufficient and needs no decoding.
675
+ """
676
+ return self.string(value).replace("-", "‐")
677
+
662
678
  # -----------------------------------------------------------------
663
679
  # JS-equivalent value stringification
664
680
  # -----------------------------------------------------------------
@@ -549,8 +549,8 @@ function Widget({ values }: { values: number[] }) {
549
549
  return <ul>{values.map((label) => (label > 0 ? <li key={label}>{label}</li> : null))}</ul>
550
550
  }
551
551
  `)
552
- expect(template).toContain('bf.comment("loop-i:" ~ bf.string(label))')
553
- expect(template).not.toContain("bf.comment(\"loop-i:\" ~ bf.string('x'))")
552
+ expect(template).toContain('bf.comment("loop-i:" ~ bf.escape_comment_key(label))')
553
+ expect(template).not.toContain("bf.comment(\"loop-i:\" ~ bf.escape_comment_key('x'))")
554
554
  })
555
555
 
556
556
  test('a loop param shadowing an outer const resolves to the row value inside a .map() preamble local initializer', () => {
@@ -19,7 +19,7 @@
19
19
  * rather than re-exposing the whole adapter.
20
20
  */
21
21
 
22
- import type { ParsedExpr, CompilerError, IRMetadata } from '@barefootjs/jsx'
22
+ import type { ParsedExpr, CompilerError, IRMetadata, LoweringMatcher } from '@barefootjs/jsx'
23
23
 
24
24
  export interface JinjaEmitContext {
25
25
  /**
@@ -28,6 +28,17 @@ export interface JinjaEmitContext {
28
28
  */
29
29
  readonly _searchParamsLocals: Set<string>
30
30
 
31
+ /**
32
+ * Registered lowering-plugin matchers (#2057), bound to this component's
33
+ * metadata at init. Read by `JinjaTopLevelEmitter`'s `lowering` seam
34
+ * (#2843) so a registered call — the built-in `queryHref`, or any
35
+ * userland plugin — is recognised no matter where it sits in an
36
+ * expression tree (a ternary branch, a template-literal interpolation, …),
37
+ * not only when it's the call the adapter's own top-level conversion
38
+ * entry point (`convertExpressionToJinja`) is asked to lower directly.
39
+ */
40
+ readonly _loweringMatchers: readonly LoweringMatcher[]
41
+
31
42
  /**
32
43
  * Inline a module-scope pure string-literal const by name as the resolved
33
44
  * literal value, or null when the name is not such a const.
@@ -51,6 +51,8 @@
51
51
  import { groupBinaryOperand,
52
52
  groupObjectLiteralSegments,
53
53
  type ParsedExprEmitter,
54
+ type LoweringEmitter,
55
+ type LoweringNode,
54
56
  type HigherOrderMethod,
55
57
  type ArrayMethod,
56
58
  type LiteralType,
@@ -61,6 +63,8 @@ import { groupBinaryOperand,
61
63
  identifierPath,
62
64
  matchSearchParamsMethodCall,
63
65
  sortComparatorFromArrow,
66
+ queryHrefArgs,
67
+ isValidHelperId,
64
68
  } from '@barefootjs/jsx'
65
69
 
66
70
  import type { JinjaEmitContext } from '../emit-context.ts'
@@ -323,6 +327,45 @@ export class JinjaTopLevelEmitter implements ParsedExprEmitter {
323
327
  this.ctx = ctx
324
328
  }
325
329
 
330
+ /**
331
+ * Registered-lowering seam (#2843): `emitParsedExpr`'s shared `call` case
332
+ * tries every matcher here BEFORE `call()` itself, so a registered call
333
+ * (the built-in `queryHref`, or any userland plugin) is recognised no
334
+ * matter where it sits in the tree. `render` is what used to live inline
335
+ * in `JinjaAdapter.convertExpressionToJinja` before the object-literal
336
+ * support-gate refusal (`checkSupport`'s `call` arm, now itself
337
+ * registry-aware) made the pre-gate special case unnecessary.
338
+ */
339
+ get lowering(): LoweringEmitter {
340
+ return {
341
+ matchers: this.ctx._loweringMatchers,
342
+ render: (node: LoweringNode, emit: (e: ParsedExpr) => string): string | null => {
343
+ // `query` guard-list — `queryHref`-shaped. The helper includes a
344
+ // pair iff its guard is truthy AND its value is a non-empty string
345
+ // (the client's `if (value)`): a plain `key: v` passes guard
346
+ // `true`, a conditional `key: cond ? v : undefined` passes the
347
+ // lowered cond. Only the `query` helper renders to `bf.query`;
348
+ // another guard-list helper must not be silently mis-rendered as
349
+ // a query.
350
+ if (node.kind === 'guard-list' && node.helper === 'query') {
351
+ const qArgs = queryHrefArgs(node, emit)
352
+ return `bf.query(${qArgs.join(', ')})`
353
+ }
354
+ // Generic `helper-call` (#2069) — the neutral vocabulary's escape
355
+ // hatch for a userland `LoweringPlugin` that lowers to a single
356
+ // runtime-helper invocation. `bf.<helper>(args…)` mirrors the
357
+ // `query` helper's own naming convention exactly: the framework
358
+ // renders the call, the plugin author registers `<helper>` as a
359
+ // Jinja-callable function/filter in their own runtime — same
360
+ // contract as `bf.query` itself, just not built in.
361
+ if (node.kind === 'helper-call' && isValidHelperId(node.helper)) {
362
+ return `bf.${node.helper}(${node.args.map(emit).join(', ')})`
363
+ }
364
+ return null
365
+ },
366
+ }
367
+ }
368
+
326
369
  identifier(name: string): string {
327
370
  // `undefined` / `null` nested inside a larger expression tree — Jinja
328
371
  // `none` (#1897).
@@ -138,8 +138,6 @@ import {
138
138
  lookupStaticRecordLiteral,
139
139
  searchParamsLocalNames,
140
140
  prepareLoweringMatchers,
141
- queryHrefArgs,
142
- isValidHelperId,
143
141
  sortComparatorFromArrow,
144
142
  isDangerousInnerHtmlAttr,
145
143
  resolveDangerousInnerHtml,
@@ -995,9 +993,12 @@ export class JinjaAdapter extends BaseAdapter implements IRNodeEmitter<JinjaRend
995
993
  // client's `mapArrayAnchored` can hydrate every SSR-rendered item by its
996
994
  // anchor. Still under the row scope (see above) — `loop.key` is a
997
995
  // per-row expression.
996
+ // `bf.escape_comment_key` (#2795 follow-up) both stringifies the key and
997
+ // neutralizes `-` so it can't spell `-->` and close the comment early —
998
+ // see its doc in `runtime.py`.
998
999
  const bodyChildren =
999
1000
  loop.bodyIsItemConditional && loop.key
1000
- ? `{{ bf.comment("loop-i:" ~ bf.string(${this.convertExpressionToJinja(loop.key)})) | safe }}\n${childrenUnderLoop}`
1001
+ ? `{{ bf.comment("loop-i:" ~ bf.escape_comment_key(${this.convertExpressionToJinja(loop.key)})) | safe }}\n${childrenUnderLoop}`
1001
1002
  : childrenUnderLoop
1002
1003
  this.scope = prevScope
1003
1004
 
@@ -1430,8 +1431,13 @@ export class JinjaAdapter extends BaseAdapter implements IRNodeEmitter<JinjaRend
1430
1431
  {
1431
1432
  const m = this.parseUndefinedAlternateTernary(value.expr)
1432
1433
  if (m) {
1433
- const cond = this.convertConditionToJinja(m.condition)
1434
- const val = this.convertExpressionToJinja(m.consequent)
1434
+ // Pass the PARSED sub-trees through as `preParsed` (#2843 review)
1435
+ // rather than re-parsing `m.condition`/`m.consequent` — those are
1436
+ // `exprToString` debug text, which renders any nested
1437
+ // `object-literal` (e.g. a registered call's params, `queryHref`'s
1438
+ // `{ tag }`) as a non-reparseable `[UNSUPPORTED: …]` placeholder.
1439
+ const cond = this.convertConditionToJinja(m.condition, m.testParsed)
1440
+ const val = this.convertExpressionToJinja(m.consequent, m.consequentParsed)
1435
1441
  return `\n{% if ${cond} %}\n${name}="{{ bf.string(${val}) }}"\n{% endif %}\n`
1436
1442
  }
1437
1443
  }
@@ -1693,7 +1699,7 @@ export class JinjaAdapter extends BaseAdapter implements IRNodeEmitter<JinjaRend
1693
1699
  const hasTaggedTemplate = /[A-Za-z_$][\w$]*\s*`/.test(probe)
1694
1700
  if (!startsAsObjectLiteral && !hasTaggedTemplate) return false
1695
1701
  const parsed = parseExpression(expr.trim())
1696
- const support = isSupported(parsed)
1702
+ const support = isSupported(parsed, { loweringMatchers: this._loweringMatchers })
1697
1703
  if (parsed.kind !== 'unsupported' && support.supported) return false
1698
1704
  const reason = support.reason ?? (parsed.kind === 'unsupported' ? parsed.reason : undefined)
1699
1705
  const reasonLine = reason ? `\n${reason}` : ''
@@ -1720,6 +1726,7 @@ export class JinjaAdapter extends BaseAdapter implements IRNodeEmitter<JinjaRend
1720
1726
  private get emitCtx(): JinjaEmitContext {
1721
1727
  return {
1722
1728
  _searchParamsLocals: this._searchParamsLocals,
1729
+ _loweringMatchers: this._loweringMatchers,
1723
1730
  _resolveModuleStringConst: (name) => this._resolveModuleStringConst(name),
1724
1731
  _resolveLiteralConst: (name) => this._resolveLiteralConst(name),
1725
1732
  _resolveStaticRecordLiteral: (o, k) => this._resolveStaticRecordLiteral(o, k),
@@ -1774,42 +1781,22 @@ export class JinjaAdapter extends BaseAdapter implements IRNodeEmitter<JinjaRend
1774
1781
  parsed = parseExpression(trimmed)
1775
1782
  }
1776
1783
 
1777
- // Registered call lowerings (#2057) including the built-in `queryHref`
1778
- // plugin (#2042), which lowers `queryHref(base, { })` to a neutral
1779
- // `guard-list` on the `query` helper `bf.query(base, <triples>)`.
1780
- // Recognised before the support gate because the object-literal arg is
1781
- // otherwise `unsupported` (BF101). The `query` helper includes a pair iff its
1782
- // guard is truthy AND its value is a non-empty string (the client's
1783
- // `if (value)`): a plain `key: v` passes guard `true`, a conditional
1784
- // `key: cond ? v : undefined` passes the lowered cond. Only the `query`
1785
- // helper renders to `bf.query`; another guard-list helper must not be
1786
- // silently mis-rendered as a query.
1787
- if (parsed.kind === 'call') {
1788
- for (const matcher of this._loweringMatchers) {
1789
- const node = matcher(parsed.callee, parsed.args)
1790
- if (node?.kind === 'guard-list' && node.helper === 'query') {
1791
- const qArgs = queryHrefArgs(node, n => this.renderParsedExprToJinja(n))
1792
- return `bf.query(${qArgs.join(', ')})`
1793
- }
1794
- // Generic `helper-call` (#2069) — the neutral vocabulary's escape
1795
- // hatch for a userland `LoweringPlugin` that lowers to a single
1796
- // runtime-helper invocation. `bf.<helper>(args…)` mirrors the
1797
- // `query` helper's own naming convention exactly: the framework
1798
- // renders the call, the plugin author registers `<helper>` as a
1799
- // Jinja-callable function/filter in their own runtime — same
1800
- // contract as `bf.query` itself, just not built in.
1801
- if (node?.kind === 'helper-call' && isValidHelperId(node.helper)) {
1802
- const argsX = node.args.map(a => this.renderParsedExprToJinja(a))
1803
- return `bf.${node.helper}(${argsX.join(', ')})`
1804
- }
1805
- }
1806
- }
1807
-
1784
+ // #2843: a registered lowering plugin's call (the built-in `queryHref`,
1785
+ // or any userland plugin) is recognised no matter where it sits in the
1786
+ // tree a ternary branch, a template-literal interpolation, … — not
1787
+ // only when `parsed.kind === 'call'` directly. That recognition now
1788
+ // lives in `JinjaTopLevelEmitter`'s `lowering` seam, consulted by
1789
+ // `emitParsedExpr`'s shared `call` dispatch; the support gate below is
1790
+ // passed `this._loweringMatchers` so a matched call's params (e.g.
1791
+ // `queryHref`'s object literal, otherwise `unsupported` at `rendered`
1792
+ // position) are admitted wherever the call is nested.
1793
+ //
1808
1794
  // `pos` distinguishes a derived-seed RHS (an assignment, checked via
1809
1795
  // `isSupportedValue`) from every other, genuinely rendered call site —
1810
1796
  // the rendered gate would otherwise re-refuse a tree the seed plan
1811
1797
  // already classified `derived` at value position (#2696 review).
1812
- const support = pos === 'value' ? isSupportedValue(parsed) : isSupported(parsed)
1798
+ const supportOpts = { loweringMatchers: this._loweringMatchers }
1799
+ const support = pos === 'value' ? isSupportedValue(parsed, supportOpts) : isSupported(parsed, supportOpts)
1813
1800
  if (!support.supported) {
1814
1801
  this.errors.push({
1815
1802
  code: 'BF101',
@@ -1875,10 +1862,21 @@ export class JinjaAdapter extends BaseAdapter implements IRNodeEmitter<JinjaRend
1875
1862
  * Parse `cond ? value : undefined` (or `: null`), returning the
1876
1863
  * condition/consequent source spans, else `null`. Used for the
1877
1864
  * attribute-omission rule (#1897).
1865
+ *
1866
+ * `testParsed`/`consequentParsed` (#2843 review) are the ACTUAL parsed
1867
+ * sub-trees, for the caller to pass through as `preParsed` — `condition`/
1868
+ * `consequent` are `exprToString` output, a DEBUG formatter that renders
1869
+ * any nested `object-literal` as the non-reparseable `[UNSUPPORTED: …]`
1870
+ * placeholder (unlike `stringifyParsedExpr`, meant for round-tripping).
1871
+ * Re-parsing THAT string for a consequent like `queryHref(base, { tag })`
1872
+ * would corrupt the call's args before the lowering registry — now
1873
+ * consulted at any nesting depth (#2843) — ever sees them. Kept for any
1874
+ * caller that only needs the string form; the parsed trees are the
1875
+ * correct input wherever the caller can accept `preParsed`.
1878
1876
  */
1879
1877
  parseUndefinedAlternateTernary(
1880
1878
  expr: string,
1881
- ): { condition: string; consequent: string } | null {
1879
+ ): { condition: string; consequent: string; testParsed: ParsedExpr; consequentParsed: ParsedExpr } | null {
1882
1880
  const parsed = parseExpression(expr.trim())
1883
1881
  if (parsed?.kind !== 'conditional') return null
1884
1882
  const alt = parsed.alternate
@@ -1893,6 +1891,8 @@ export class JinjaAdapter extends BaseAdapter implements IRNodeEmitter<JinjaRend
1893
1891
  return {
1894
1892
  condition: exprToString(parsed.test),
1895
1893
  consequent: exprToString(parsed.consequent),
1894
+ testParsed: parsed.test,
1895
+ consequentParsed: parsed.consequent,
1896
1896
  }
1897
1897
  }
1898
1898
 
@@ -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
+ // `JinjaTopLevelEmitter`'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`) SSRs an empty `<ul>` on real Jinja2 — 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 = {}
@@ -613,6 +613,26 @@ function buildPythonProps(
613
613
  }
614
614
  }
615
615
 
616
+ // #2813: `extractSsrDefaults` can seed an entry under a name that is
617
+ // none of `propsParams` / `signals` / `memos` above — a bare local-const
618
+ // alias of a signal/memo getter (`const items__alias = items`,
619
+ // `ssr-defaults.ts`) seeds `items__alias` alongside `items`. Production
620
+ // hands the whole manifest-derived stash straight through
621
+ // (`deriveStashFromDefaults` IS the production seeding mechanism here,
622
+ // not a harness stand-in), so any leftover `derivedProps` key must be
623
+ // pushed too, or this harness would silently omit what production
624
+ // actually provides, mismeasuring this conformance case.
625
+ const seededNames = new Set<string>([
626
+ ...ir.metadata.propsParams.filter(p => !p.isRest).map(p => p.name),
627
+ ...(restPropsName ? [restPropsName] : []),
628
+ ...ir.metadata.signals.filter(s => !s.envReader).map(s => s.getter),
629
+ ...ir.metadata.memos.map(m => m.name),
630
+ ])
631
+ for (const [name, value] of Object.entries(derivedProps)) {
632
+ if (seededNames.has(name)) continue
633
+ fullEntries.push(`${pyStr(name)}: ${toPyLiteral(value)}`)
634
+ }
635
+
616
636
  // (#1922) Request-scoped `searchParams()`: bind `searchParams` to an
617
637
  // empty-query reader (so the render script needn't build one from a real
618
638
  // request). The conformance harness issues no query string (the