@barefootjs/rust 0.33.4 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/rust",
3
- "version": "0.33.4",
3
+ "version": "0.34.0",
4
4
  "description": "minijinja (Rust) adapter for BarefootJS — compiles IR to .j2 templates and ships a Rust rendering runtime (packages/adapter-rust/runtime/); runs under any Rust web framework (axum, etc.)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -54,7 +54,7 @@
54
54
  "directory": "packages/adapter-rust"
55
55
  },
56
56
  "dependencies": {
57
- "@barefootjs/shared": "0.33.4"
57
+ "@barefootjs/shared": "0.34.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.4",
75
- "@barefootjs/vite": "0.33.4",
76
- "@barefootjs/client": "0.33.4",
74
+ "@barefootjs/jsx": "0.34.0",
75
+ "@barefootjs/vite": "0.34.0",
76
+ "@barefootjs/client": "0.34.0",
77
77
  "typescript": "^5.0.0",
78
78
  "vite": "^6.0.0"
79
79
  }
@@ -20,7 +20,7 @@
20
20
  * rather than re-exposing the whole adapter.
21
21
  */
22
22
 
23
- import type { ParsedExpr, CompilerError, IRMetadata } from '@barefootjs/jsx'
23
+ import type { ParsedExpr, CompilerError, IRMetadata, LoweringMatcher } from '@barefootjs/jsx'
24
24
 
25
25
  export interface JinjaEmitContext {
26
26
  /**
@@ -29,6 +29,17 @@ export interface JinjaEmitContext {
29
29
  */
30
30
  readonly _searchParamsLocals: Set<string>
31
31
 
32
+ /**
33
+ * Registered lowering-plugin matchers (#2057), bound to this component's
34
+ * metadata at init. Read by `JinjaTopLevelEmitter`'s `lowering` seam
35
+ * (#2843) so a registered call — the built-in `queryHref`, or any
36
+ * userland plugin — is recognised no matter where it sits in an
37
+ * expression tree (a ternary branch, a template-literal interpolation, …),
38
+ * not only when it's the call the adapter's own top-level conversion
39
+ * entry point (`convertExpressionToJinja`) is asked to lower directly.
40
+ */
41
+ readonly _loweringMatchers: readonly LoweringMatcher[]
42
+
32
43
  /**
33
44
  * Inline a module-scope pure string-literal const by name as the resolved
34
45
  * literal value, or null when the name is not such a const.
@@ -60,6 +60,10 @@ import { groupBinaryOperand,
60
60
  identifierPath,
61
61
  matchSearchParamsMethodCall,
62
62
  sortComparatorFromArrow,
63
+ type LoweringEmitter,
64
+ type LoweringNode,
65
+ queryHrefArgs,
66
+ isValidHelperId,
63
67
  } from '@barefootjs/jsx'
64
68
 
65
69
  import type { JinjaEmitContext } from '../emit-context.ts'
@@ -314,6 +318,35 @@ export class JinjaTopLevelEmitter implements ParsedExprEmitter {
314
318
  this.ctx = ctx
315
319
  }
316
320
 
321
+ /**
322
+ * Registered-lowering seam (#2843): `emitParsedExpr`'s shared `call` case
323
+ * tries every matcher here BEFORE `call()` itself, so a registered call
324
+ * (the built-in `queryHref`, or any userland plugin) is recognised no
325
+ * matter where it sits in the tree. `render` is what used to live inline
326
+ * in `MinijinjaAdapter.convertExpressionToJinja` before the object-literal
327
+ * support-gate refusal (`checkSupport`'s `call` arm, now itself
328
+ * registry-aware) made the pre-gate special case unnecessary.
329
+ */
330
+ get lowering(): LoweringEmitter {
331
+ return {
332
+ matchers: this.ctx._loweringMatchers,
333
+ render: (node: LoweringNode, emit: (e: ParsedExpr) => string): string | null => {
334
+ // `query` guard-list — `queryHref`-shaped.
335
+ if (node.kind === 'guard-list' && node.helper === 'query') {
336
+ const qArgs = queryHrefArgs(node, emit)
337
+ return `bf.query(${qArgs.join(', ')})`
338
+ }
339
+ // Generic `helper-call` (#2069) — a userland `LoweringPlugin`'s
340
+ // single runtime-helper invocation; `bf.<helper>(args…)` mirrors
341
+ // the `query` helper's own naming convention.
342
+ if (node.kind === 'helper-call' && isValidHelperId(node.helper)) {
343
+ return `bf.${node.helper}(${node.args.map(emit).join(', ')})`
344
+ }
345
+ return null
346
+ },
347
+ }
348
+ }
349
+
317
350
  identifier(name: string): string {
318
351
  // `undefined` / `null` nested inside a larger expression tree — Jinja
319
352
  // `none` (#1897).
@@ -163,8 +163,6 @@ import {
163
163
  lookupStaticRecordLiteral,
164
164
  searchParamsLocalNames,
165
165
  prepareLoweringMatchers,
166
- queryHrefArgs,
167
- isValidHelperId,
168
166
  sortComparatorFromArrow,
169
167
  isDangerousInnerHtmlAttr,
170
168
  resolveDangerousInnerHtml,
@@ -173,6 +171,7 @@ import {
173
171
  resolveStaticLoopSource,
174
172
  derivesScopeFromSlot,
175
173
  BindingScope,
174
+ buildImportAliasMap,
176
175
  } from '@barefootjs/jsx'
177
176
  import { isAriaBooleanAttr, isBooleanResultExpr, isExplicitStringCall } from './boolean-result.ts'
178
177
  import type { ParsedExpr, LoweringMatcher, LoopBindingPathSegment } from '@barefootjs/jsx'
@@ -333,6 +332,18 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
333
332
  */
334
333
  private nullableOptionalProps: Set<string> = new Set()
335
334
 
335
+ /**
336
+ * Local alias -> declared/exported name for imported components (#2822,
337
+ * the SSR-side counterpart of #2777's client-JS registry-key fix). A
338
+ * child referenced under an import alias (`import { Foo as Bar }`,
339
+ * `<Bar/>`) must build its cross-template call against the child's own
340
+ * declared name (`Foo`, what `foo.tsx` registers its minijinja partial
341
+ * as) — never the caller-local binding. Built once per compile from
342
+ * `ir.metadata.imports` via the shared `buildImportAliasMap`
343
+ * (`@barefootjs/jsx`) and read by `toTemplateName`.
344
+ */
345
+ private importAliases: Map<string, string> = new Map()
346
+
336
347
  constructor(options: MinijinjaAdapterOptions = {}) {
337
348
  super()
338
349
  this.options = {
@@ -361,6 +372,7 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
361
372
  this.moduleStringConsts = collectModuleStringConsts(ir.metadata.localConstants)
362
373
  this._searchParamsLocals = searchParamsLocalNames(ir.metadata)
363
374
  this._loweringMatchers = prepareLoweringMatchers(ir.metadata)
375
+ this.importAliases = buildImportAliasMap(ir.metadata.imports ?? [])
364
376
  this.errors = []
365
377
  this.childrenCaptureCounter = 0
366
378
 
@@ -1308,8 +1320,12 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
1308
1320
  private presenceVarCounter = 0
1309
1321
 
1310
1322
  private toTemplateName(componentName: string): string {
1323
+ // Resolve an import alias (`import { Foo as Bar }`, `<Bar/>`) back to
1324
+ // the child's own declared name BEFORE snake-casing (#2822) — `Bar`
1325
+ // has no `foo.tsx`-registered partial; only `Foo` does.
1326
+ const declaredName = this.importAliases.get(componentName) ?? componentName
1311
1327
  // Convert PascalCase to snake_case for template naming.
1312
- return componentName
1328
+ return declaredName
1313
1329
  .replace(/([A-Z])/g, '_$1')
1314
1330
  .toLowerCase()
1315
1331
  .replace(/^_/, '')
@@ -1464,8 +1480,8 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
1464
1480
  {
1465
1481
  const m = this.parseUndefinedAlternateTernary(value.expr)
1466
1482
  if (m) {
1467
- const cond = this.convertConditionToJinja(m.condition)
1468
- const val = this.convertExpressionToJinja(m.consequent)
1483
+ const cond = this.convertConditionToJinja('', m.testParsed)
1484
+ const val = this.convertExpressionToJinja('', m.consequentParsed)
1469
1485
  return `\n{% if ${cond} %}\n${name}="{{ bf.string(${val}) }}"\n{% endif %}\n`
1470
1486
  }
1471
1487
  }
@@ -1733,7 +1749,7 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
1733
1749
  const hasTaggedTemplate = /[A-Za-z_$][\w$]*\s*`/.test(probe)
1734
1750
  if (!startsAsObjectLiteral && !hasTaggedTemplate) return false
1735
1751
  const parsed = parseExpression(expr.trim())
1736
- const support = isSupported(parsed)
1752
+ const support = isSupported(parsed, { loweringMatchers: this._loweringMatchers })
1737
1753
  if (parsed.kind !== 'unsupported' && support.supported) return false
1738
1754
  const reason = support.reason ?? (parsed.kind === 'unsupported' ? parsed.reason : undefined)
1739
1755
  const reasonLine = reason ? `\n${reason}` : ''
@@ -1760,6 +1776,7 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
1760
1776
  private get emitCtx(): JinjaEmitContext {
1761
1777
  return {
1762
1778
  _searchParamsLocals: this._searchParamsLocals,
1779
+ _loweringMatchers: this._loweringMatchers,
1763
1780
  _resolveModuleStringConst: (name) => this._resolveModuleStringConst(name),
1764
1781
  _resolveLiteralConst: (name) => this._resolveLiteralConst(name),
1765
1782
  _resolveStaticRecordLiteral: (o, k) => this._resolveStaticRecordLiteral(o, k),
@@ -1814,42 +1831,16 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
1814
1831
  parsed = parseExpression(trimmed)
1815
1832
  }
1816
1833
 
1817
- // Registered call lowerings (#2057) — including the built-in `queryHref`
1818
- // plugin (#2042), which lowers `queryHref(base, { … })` to a neutral
1819
- // `guard-list` on the `query` helper → `bf.query(base, <triples>)`.
1820
- // Recognised before the support gate because the object-literal arg is
1821
- // otherwise `unsupported` (BF101). The `query` helper includes a pair iff its
1822
- // guard is truthy AND its value is a non-empty string (the client's
1823
- // `if (value)`): a plain `key: v` passes guard `true`, a conditional
1824
- // `key: cond ? v : undefined` passes the lowered cond. Only the `query`
1825
- // helper renders to `bf.query`; another guard-list helper must not be
1826
- // silently mis-rendered as a query.
1827
- if (parsed.kind === 'call') {
1828
- for (const matcher of this._loweringMatchers) {
1829
- const node = matcher(parsed.callee, parsed.args)
1830
- if (node?.kind === 'guard-list' && node.helper === 'query') {
1831
- const qArgs = queryHrefArgs(node, n => this.renderParsedExprToJinja(n))
1832
- return `bf.query(${qArgs.join(', ')})`
1833
- }
1834
- // Generic `helper-call` (#2069) — the neutral vocabulary's escape
1835
- // hatch for a userland `LoweringPlugin` that lowers to a single
1836
- // runtime-helper invocation. `bf.<helper>(args…)` mirrors the
1837
- // `query` helper's own naming convention exactly: the framework
1838
- // renders the call, the plugin author registers `<helper>` as a
1839
- // MiniJinja-callable function in their own runtime — same contract
1840
- // as `bf.query` itself, just not built in.
1841
- if (node?.kind === 'helper-call' && isValidHelperId(node.helper)) {
1842
- const argsX = node.args.map(a => this.renderParsedExprToJinja(a))
1843
- return `bf.${node.helper}(${argsX.join(', ')})`
1844
- }
1845
- }
1846
- }
1847
-
1848
1834
  // `pos` distinguishes a derived-seed RHS (an assignment, checked via
1849
1835
  // `isSupportedValue`) from every other, genuinely rendered call site —
1850
1836
  // the rendered gate would otherwise re-refuse a tree the seed plan
1851
1837
  // already classified `derived` at value position (#2696 review).
1852
- const support = pos === 'value' ? isSupportedValue(parsed) : isSupported(parsed)
1838
+ // Registered call lowerings (#2057) including the built-in `queryHref`
1839
+ // plugin — are consulted by this gate no matter where the call sits in
1840
+ // the tree (`JinjaTopLevelEmitter`'s `lowering` seam does the actual
1841
+ // rendering below, via `emitParsedExpr`'s shared `call` case).
1842
+ const supportOpts = { loweringMatchers: this._loweringMatchers }
1843
+ const support = pos === 'value' ? isSupportedValue(parsed, supportOpts) : isSupported(parsed, supportOpts)
1853
1844
  if (!support.supported) {
1854
1845
  this.errors.push({
1855
1846
  code: 'BF101',
@@ -1918,7 +1909,7 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
1918
1909
  */
1919
1910
  parseUndefinedAlternateTernary(
1920
1911
  expr: string,
1921
- ): { condition: string; consequent: string } | null {
1912
+ ): { condition: string; consequent: string; testParsed: ParsedExpr; consequentParsed: ParsedExpr } | null {
1922
1913
  const parsed = parseExpression(expr.trim())
1923
1914
  if (parsed?.kind !== 'conditional') return null
1924
1915
  const alt = parsed.alternate
@@ -1926,13 +1917,16 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
1926
1917
  (alt.kind === 'identifier' && (alt.name === 'undefined' || alt.name === 'null')) ||
1927
1918
  (alt.kind === 'literal' && (alt.value === null || alt.value === undefined))
1928
1919
  if (!isUndef) return null
1929
- // Serialise the parsed sub-expressions back to JS source rather than
1930
- // slicing `expr` text `indexOf('?')` / `lastIndexOf(':')` would
1931
- // mis-split when the consequent itself contains `?` / `:` inside a
1932
- // string or nested ternary (`cond ? 'a:b' : undefined`).
1920
+ // `condition`/`consequent` are DEBUG text only (`exprToString` renders an
1921
+ // unsupported nested shape like an object literal as non-reparseable
1922
+ // `[UNSUPPORTED: …]` text) callers that need to re-lower the
1923
+ // sub-expression must use `testParsed`/`consequentParsed` (the actual
1924
+ // parsed trees) as `preParsed`, not re-parse these strings.
1933
1925
  return {
1934
1926
  condition: exprToString(parsed.test),
1935
1927
  consequent: exprToString(parsed.consequent),
1928
+ testParsed: parsed.test,
1929
+ consequentParsed: parsed.consequent,
1936
1930
  }
1937
1931
  }
1938
1932
 
@@ -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' }],
@@ -64,4 +68,11 @@ export const conformancePins: ConformancePins = {
64
68
  // `rich-prop-client-read` above.
65
69
  'jsx-element-prop-ternary': [{ code: 'BF021', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2667' }],
66
70
  'jsx-element-prop-array': [{ code: 'BF021', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2667' }],
71
+ // #2771: a reactive primitive invoked through a namespace import
72
+ // (`import * as bf from '@barefootjs/client'`, `bf.createSignal(...)`)
73
+ // that the analyzer's checker-less fast path cannot recognize refuses
74
+ // loudly (BF013) instead of silently dropping the declaration — fired
75
+ // in the shared analyzer pass ahead of any adapter's `adapter.generate()`,
76
+ // so all nine adapters (including Hono) pin this identically.
77
+ 'namespace-import-primitive': [{ code: 'BF013', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2771' }],
67
78
  }
@@ -17,4 +17,7 @@ import type { RenderDivergences } from '@barefootjs/jsx'
17
17
  // at value position and the runtime evaluator's `object-literal` case
18
18
  // now merges it, so the seed classifies `derived` and SSRs identically
19
19
  // to Hono.
20
- export const renderDivergences: RenderDivergences = {}
20
+ export const renderDivergences: RenderDivergences = {
21
+ 'aliased-loop-source':
22
+ 'A `.map()` loop whose source is a local const alias of a signal getter (`const items__alias = items`) SSRs an empty `<ul>` through real minijinja — 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.',
23
+ }