@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/dist/adapter/emit-context.d.ts +11 -1
- package/dist/adapter/emit-context.d.ts.map +1 -1
- package/dist/adapter/expr/emitters.d.ts +11 -1
- package/dist/adapter/expr/emitters.d.ts.map +1 -1
- package/dist/adapter/index.js +33 -23
- package/dist/adapter/minijinja-adapter.d.ts +14 -0
- package/dist/adapter/minijinja-adapter.d.ts.map +1 -1
- package/dist/conformance-pins.d.ts.map +1 -1
- package/dist/index.js +38 -25
- package/dist/render-divergences.d.ts.map +1 -1
- package/dist/vite.js +197 -135
- package/package.json +5 -5
- package/src/adapter/emit-context.ts +12 -1
- package/src/adapter/expr/emitters.ts +33 -0
- package/src/adapter/minijinja-adapter.ts +37 -43
- package/src/conformance-pins.ts +11 -0
- package/src/render-divergences.ts +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/rust",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
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.
|
|
75
|
-
"@barefootjs/vite": "0.
|
|
76
|
-
"@barefootjs/client": "0.
|
|
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
|
|
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.
|
|
1468
|
-
const val = this.convertExpressionToJinja(m.
|
|
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
|
-
|
|
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
|
-
//
|
|
1930
|
-
//
|
|
1931
|
-
//
|
|
1932
|
-
//
|
|
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
|
|
package/src/conformance-pins.ts
CHANGED
|
@@ -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
|
+
}
|