@barefootjs/rust 0.1.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/README.md +194 -0
- package/dist/adapter/analysis/component-tree.d.ts +26 -0
- package/dist/adapter/analysis/component-tree.d.ts.map +1 -0
- package/dist/adapter/boolean-result.d.ts +85 -0
- package/dist/adapter/boolean-result.d.ts.map +1 -0
- package/dist/adapter/emit-context.d.ts +107 -0
- package/dist/adapter/emit-context.d.ts.map +1 -0
- package/dist/adapter/expr/array-method.d.ts +75 -0
- package/dist/adapter/expr/array-method.d.ts.map +1 -0
- package/dist/adapter/expr/emitters.d.ts +143 -0
- package/dist/adapter/expr/emitters.d.ts.map +1 -0
- package/dist/adapter/index.d.ts +6 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/index.js +189091 -0
- package/dist/adapter/lib/constants.d.ts +25 -0
- package/dist/adapter/lib/constants.d.ts.map +1 -0
- package/dist/adapter/lib/ir-scope.d.ts +50 -0
- package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
- package/dist/adapter/lib/minijinja-naming.d.ts +64 -0
- package/dist/adapter/lib/minijinja-naming.d.ts.map +1 -0
- package/dist/adapter/lib/types.d.ts +32 -0
- package/dist/adapter/lib/types.d.ts.map +1 -0
- package/dist/adapter/memo/seed.d.ts +84 -0
- package/dist/adapter/memo/seed.d.ts.map +1 -0
- package/dist/adapter/minijinja-adapter.d.ts +421 -0
- package/dist/adapter/minijinja-adapter.d.ts.map +1 -0
- package/dist/adapter/props/prop-classes.d.ts +33 -0
- package/dist/adapter/props/prop-classes.d.ts.map +1 -0
- package/dist/adapter/spread/spread-codegen.d.ts +63 -0
- package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
- package/dist/adapter/value/parsed-literal.d.ts +28 -0
- package/dist/adapter/value/parsed-literal.d.ts.map +1 -0
- package/dist/build.d.ts +29 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +189111 -0
- package/dist/conformance-pins.d.ts +13 -0
- package/dist/conformance-pins.d.ts.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +189112 -0
- package/package.json +67 -0
- package/runtime/Cargo.lock +124 -0
- package/runtime/Cargo.toml +21 -0
- package/runtime/src/backend_minijinja.rs +176 -0
- package/runtime/src/bin/bf-render.rs +147 -0
- package/runtime/src/evaluator.rs +770 -0
- package/runtime/src/lib.rs +19 -0
- package/runtime/src/manifest.rs +258 -0
- package/runtime/src/num.rs +558 -0
- package/runtime/src/runtime.rs +1548 -0
- package/runtime/src/search_params.rs +173 -0
- package/runtime/tests/eval_vectors.rs +94 -0
- package/runtime/tests/evaluator.rs +407 -0
- package/runtime/tests/helper_vectors.rs +348 -0
- package/runtime/tests/manifest.rs +169 -0
- package/runtime/tests/omit.rs +79 -0
- package/runtime/tests/props_attr.rs +75 -0
- package/runtime/tests/query.rs +50 -0
- package/runtime/tests/render_child.rs +210 -0
- package/runtime/tests/search_params.rs +68 -0
- package/runtime/tests/spread_attrs.rs +94 -0
- package/runtime/tests/template_primitives.rs +376 -0
- package/runtime/tests/vector-divergences.json +33 -0
- package/src/__tests__/minijinja-adapter-unit.test.ts +392 -0
- package/src/__tests__/minijinja-adapter.test.ts +58 -0
- package/src/__tests__/minijinja-counter.test.ts +61 -0
- package/src/__tests__/minijinja-query-href.test.ts +101 -0
- package/src/__tests__/minijinja-spread-attrs.test.ts +227 -0
- package/src/adapter/analysis/component-tree.ts +119 -0
- package/src/adapter/boolean-result.ts +177 -0
- package/src/adapter/emit-context.ts +119 -0
- package/src/adapter/expr/array-method.ts +346 -0
- package/src/adapter/expr/emitters.ts +608 -0
- package/src/adapter/index.ts +6 -0
- package/src/adapter/lib/constants.ts +37 -0
- package/src/adapter/lib/ir-scope.ts +95 -0
- package/src/adapter/lib/minijinja-naming.ts +85 -0
- package/src/adapter/lib/types.ts +35 -0
- package/src/adapter/memo/seed.ts +135 -0
- package/src/adapter/minijinja-adapter.ts +1796 -0
- package/src/adapter/props/prop-classes.ts +65 -0
- package/src/adapter/spread/spread-codegen.ts +168 -0
- package/src/adapter/value/parsed-literal.ts +76 -0
- package/src/build.ts +38 -0
- package/src/conformance-pins.ts +101 -0
- package/src/index.ts +12 -0
- package/src/test-render.ts +680 -0
package/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# @barefootjs/rust
|
|
2
|
+
|
|
3
|
+
minijinja (Rust) adapter for BarefootJS: compiles the BarefootJS IR (JSX →
|
|
4
|
+
IR, see `spec/compiler.md`) into `.j2` template files plus the client JS
|
|
5
|
+
bundle every other adapter produces, and ships a Rust rendering runtime
|
|
6
|
+
(`runtime/`, crate `barefootjs`) that renders those templates through a
|
|
7
|
+
plain [`minijinja::Environment`](https://docs.rs/minijinja) — no framework
|
|
8
|
+
is required (axum, actix-web, warp, bare `hyper`, etc. all work the same
|
|
9
|
+
way).
|
|
10
|
+
|
|
11
|
+
Near-verbatim port of `@barefootjs/jinja` (the Jinja2/Python adapter) to the
|
|
12
|
+
`minijinja` Rust crate. **The emitted template syntax is IDENTICAL** to
|
|
13
|
+
`@barefootjs/jinja`'s output — minijinja 2.21 is Jinja2-compatible for
|
|
14
|
+
everything this adapter emits (verified by an orchestrator spike; see the
|
|
15
|
+
Environment contract below). Only identity fields differ (`name: 'minijinja'`,
|
|
16
|
+
`extension: '.j2'`, class `MinijinjaAdapter`) plus the render engine that
|
|
17
|
+
interprets the syntax at request time (a Rust `minijinja::Environment`
|
|
18
|
+
instead of Python's `jinja2.Environment`). See
|
|
19
|
+
`src/adapter/minijinja-adapter.ts`'s header comment for the full
|
|
20
|
+
Kolon↔Jinja2 syntax-mapping table (inherited unchanged from `@barefootjs/jinja`)
|
|
21
|
+
and the JS-semantics divergences this port handles uniformly (truthiness,
|
|
22
|
+
stringification, reserved-word identifier mangling, and the
|
|
23
|
+
evaluator-only higher-order-callback lowering since Jinja has no lambda
|
|
24
|
+
expression).
|
|
25
|
+
|
|
26
|
+
## Template output shape
|
|
27
|
+
|
|
28
|
+
- `name: 'minijinja'`, `extension: '.j2'`, `templatesPerComponent: true` —
|
|
29
|
+
one `.j2` file per component, named by snake-casing the PascalCase
|
|
30
|
+
component name (`UserCard` → `user_card.j2`).
|
|
31
|
+
- Hydration markers (`bf-s`, `bf-h`/`bf-m`/`bf-r`, `bf-p`, slot/conditional
|
|
32
|
+
comment markers, loop boundary comments) use the SAME runtime method
|
|
33
|
+
names as every other adapter's `bf.*` calls (`bf.scope_attr()`,
|
|
34
|
+
`bf.hydration_attrs()`, `bf.text_start`/`text_end`, `bf.comment(...)`,
|
|
35
|
+
…) — see `spec/template-helpers.md` for the shared helper contract.
|
|
36
|
+
- Every text/attribute interpolation of a possibly-non-string value is
|
|
37
|
+
routed through `bf.string(...)` (or `bf.bool_str(...)` for
|
|
38
|
+
boolean-shaped values); every non-comparison condition position is
|
|
39
|
+
routed through `bf.truthy(...)`. Both are pure Rust-runtime helpers —
|
|
40
|
+
see the Rust runtime pointer below.
|
|
41
|
+
|
|
42
|
+
## The minijinja Environment contract
|
|
43
|
+
|
|
44
|
+
This adapter's output assumes an `Environment` constructed exactly as
|
|
45
|
+
follows (see `runtime/src/backend_minijinja.rs`):
|
|
46
|
+
|
|
47
|
+
```rust
|
|
48
|
+
let mut env = Environment::new();
|
|
49
|
+
env.set_loader(minijinja::path_loader(templates_dir)); // .j2 files
|
|
50
|
+
env.set_undefined_behavior(UndefinedBehavior::Chainable); // == Jinja2's ChainableUndefined; `missing.deep` renders '' — verified
|
|
51
|
+
env.set_trim_blocks(true);
|
|
52
|
+
env.set_lstrip_blocks(true);
|
|
53
|
+
env.set_auto_escape_callback(|_| AutoEscape::Html); // REQUIRED: .j2 is not auto-escaped by default in minijinja
|
|
54
|
+
env.set_formatter(<custom formatter>);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`trim_blocks`/`lstrip_blocks` are required because this adapter places
|
|
58
|
+
`{% … %}` control tags on their own source line; without them every such
|
|
59
|
+
line would leak a stray newline/indentation into the rendered HTML.
|
|
60
|
+
|
|
61
|
+
The **custom formatter** is the uniform emit policy that absorbs the
|
|
62
|
+
remaining JS/minijinja semantic differences (no per-fixture hacks anywhere —
|
|
63
|
+
all divergence-absorption lives here plus in the `bf` runtime helpers):
|
|
64
|
+
|
|
65
|
+
- `undefined`/`none` → print nothing
|
|
66
|
+
- safe values → raw passthrough
|
|
67
|
+
- strings → escape with MarkupSafe-compatible entities: `&` → `&`, `<` →
|
|
68
|
+
`<`, `>` → `>`, `"` → `"`, `'` → `'` — **not** minijinja's
|
|
69
|
+
own default `'`; the conformance fixtures pin `'` (matching
|
|
70
|
+
Python's MarkupSafe, which `@barefootjs/jinja`'s output also relies on)
|
|
71
|
+
- numbers → JS `String(n)` formatting (`1.0` → `1`) via the centralized
|
|
72
|
+
`format_js_number` — this is a **fallback**; templates normally already
|
|
73
|
+
route a value through `bf.string(...)` before it reaches the formatter
|
|
74
|
+
- bools → `true`/`false`
|
|
75
|
+
|
|
76
|
+
### minijinja↔Jinja2 divergence record (orchestrator spike, minijinja 2.21.0)
|
|
77
|
+
|
|
78
|
+
Verified compatible before committing to the near-verbatim port strategy:
|
|
79
|
+
|
|
80
|
+
- `{{ missing }}` → `''`; `{% if missing.deep %}` → falsy, renders `''`
|
|
81
|
+
under `UndefinedBehavior::Chainable`
|
|
82
|
+
- `(x if (x is defined and x is not none) else 'FB')` → `'FB'` for both
|
|
83
|
+
undefined AND `none`, the value otherwise ⇒ `@barefootjs/jinja`'s `??`
|
|
84
|
+
lowering ports verbatim
|
|
85
|
+
- `{% set cap %}…{% endset %}` works; dict literals, `elif`, `loop.index0`,
|
|
86
|
+
`{% set %}` inside `{% for %}`, `~` concat, `| safe` all work identically
|
|
87
|
+
to Python's Jinja2
|
|
88
|
+
- method calls on a custom `Object` (`bf.to_str(42)`) work via
|
|
89
|
+
`Object::call_method`
|
|
90
|
+
- minijinja truthiness is Python-like (empty list/map/str are falsy) — same
|
|
91
|
+
as Python's Jinja2, and the reason JS truthiness MUST go through
|
|
92
|
+
`bf.truthy(...)` (the adapter already emits this, unchanged from the
|
|
93
|
+
Jinja2 port)
|
|
94
|
+
|
|
95
|
+
No divergence found that required a per-fixture special case; every
|
|
96
|
+
absorption point above is uniform (formatter, `bf` helper, or adapter emit
|
|
97
|
+
rule).
|
|
98
|
+
|
|
99
|
+
## Rust runtime
|
|
100
|
+
|
|
101
|
+
`runtime/` is a self-contained Rust crate (`barefootjs`, deps: `minijinja`,
|
|
102
|
+
`serde`, `serde_json`) implementing the engine-agnostic `bf` object every
|
|
103
|
+
emitted template calls into: hydration markers, context propagation
|
|
104
|
+
(`provide_context`/`use_context`), child-component rendering
|
|
105
|
+
(`render_child`), script registration, and the JS-compatible helper library
|
|
106
|
+
(`string`, `bool_str`, `truthy`, `number`, `floor`/`ceil`/`round`,
|
|
107
|
+
array/string helpers, the `*_eval` evaluator helpers, `spread_attrs`,
|
|
108
|
+
`query`, …). It mirrors the layering of `packages/adapter-jinja/python/barefootjs/`
|
|
109
|
+
(an engine-agnostic core plus a thin per-engine backend) — see
|
|
110
|
+
`spec/template-helpers.md` for the semantic contract each helper must
|
|
111
|
+
satisfy, and the crate's own tests under `runtime/tests/`.
|
|
112
|
+
|
|
113
|
+
The crate also ships a `bf-render` binary — a conformance renderer that
|
|
114
|
+
reads a JSON payload (`templates_dir`, `entry`, `scope_id`, `vars`,
|
|
115
|
+
`search_params?`, `children[]`) and writes the rendered HTML to stdout. It
|
|
116
|
+
is what `src/test-render.ts` builds (once, memoized — `cargo build
|
|
117
|
+
--manifest-path runtime/Cargo.toml --bin bf-render`) and spawns per
|
|
118
|
+
conformance fixture; a production host would instead link the `barefootjs`
|
|
119
|
+
library crate directly (see the axum sketch below) rather than shelling out
|
|
120
|
+
to the binary.
|
|
121
|
+
|
|
122
|
+
## Usage
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// barefoot.config.ts
|
|
126
|
+
import { createConfig } from '@barefootjs/rust/build'
|
|
127
|
+
|
|
128
|
+
export default createConfig({
|
|
129
|
+
components: ['./src/components'],
|
|
130
|
+
outDir: './dist',
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`bf build` then emits `.j2` templates + client JS under `outDir`. A Rust
|
|
135
|
+
host renders a component by constructing a `minijinja::Environment` (per
|
|
136
|
+
the contract above) over `minijinja::path_loader` pointed at the emitted
|
|
137
|
+
templates, wiring in the `barefootjs` crate's `backend_minijinja` module as
|
|
138
|
+
the render backend:
|
|
139
|
+
|
|
140
|
+
```rust
|
|
141
|
+
use axum::{routing::get, Router};
|
|
142
|
+
use barefootjs::{backend_minijinja::render_named, BarefootJS};
|
|
143
|
+
|
|
144
|
+
async fn user_card() -> axum::response::Html<String> {
|
|
145
|
+
let mut bf = BarefootJS::new();
|
|
146
|
+
let html = render_named("user_card", &mut bf, /* vars */ Default::default());
|
|
147
|
+
axum::response::Html(html)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#[tokio::main]
|
|
151
|
+
async fn main() {
|
|
152
|
+
let app = Router::new().route("/", get(user_card));
|
|
153
|
+
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
|
154
|
+
axum::serve(listener, app).await.unwrap();
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Divergences from `@barefootjs/jinja`
|
|
159
|
+
|
|
160
|
+
Beyond the identity fields (`name`, `extension`, class name) and the render
|
|
161
|
+
engine itself, this is a near-verbatim port. One divergence DOES reach the
|
|
162
|
+
emitted template syntax:
|
|
163
|
+
|
|
164
|
+
- **Record/map lookup with a default.** Jinja2's dict has a `.get(k, d)`
|
|
165
|
+
method; `@barefootjs/jinja` emits `{'a': 'x', ...}.get(key, '')` for a
|
|
166
|
+
`Record<K, string>[key]` template-literal lookup. minijinja maps have no
|
|
167
|
+
`.get` method (`unknown method: map has no method named get`, verified).
|
|
168
|
+
This adapter instead emits `({'a': 'x', ...}[key] | default(''))`: a
|
|
169
|
+
missing-key index on a minijinja map returns `undefined` under
|
|
170
|
+
`UndefinedBehavior::Chainable`, and the builtin `default` filter supplies
|
|
171
|
+
the fallback inline (verified: `{{ {'a':'x'}[k] | default('DD') }}` →
|
|
172
|
+
`'DD'` on miss, `'x'` on hit, including nested in call args/string
|
|
173
|
+
concat). See `convertTemplateLiteralPartsToJinja`'s `lookup` branch in
|
|
174
|
+
`src/adapter/minijinja-adapter.ts`.
|
|
175
|
+
|
|
176
|
+
The remaining divergences are all in the TypeScript side's places the
|
|
177
|
+
conformance harness could not port byte-for-byte from `@barefootjs/jinja`,
|
|
178
|
+
confined to `src/test-render.ts` (the conformance-render harness, not the
|
|
179
|
+
adapter itself — the emitted template syntax is unaffected beyond the point
|
|
180
|
+
above):
|
|
181
|
+
|
|
182
|
+
- **Render invocation.** `@barefootjs/jinja` generates a throwaway Python
|
|
183
|
+
*script* per fixture (inline Python source building a props dict +
|
|
184
|
+
per-child renderer closures) and shells out to `python3`. This adapter
|
|
185
|
+
instead serializes the same information to a JSON *payload* and spawns
|
|
186
|
+
one long-lived compiled `bf-render` binary, built once per test run.
|
|
187
|
+
- **Non-finite numbers.** JSON cannot represent `NaN`/`Infinity`/`-Infinity`
|
|
188
|
+
— the TS side recursively encodes them as `{"__bf_special": "nan" | "inf"
|
|
189
|
+
| "-inf"}` before `JSON.stringify`; `bf-render` decodes the sentinel back
|
|
190
|
+
to the corresponding `f64` after parsing.
|
|
191
|
+
- **`vars` keys are unmangled.** Like `@barefootjs/jinja`'s Python props
|
|
192
|
+
dict, the JSON payload's `vars` keys are the RAW prop/signal/memo names;
|
|
193
|
+
reserved-word mangling happens in ONE place backend-side (the Rust
|
|
194
|
+
runtime's `render_named`, mirroring the Python runtime's `render_named`).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component-tree analysis for the minijinja template adapter.
|
|
3
|
+
*
|
|
4
|
+
* Ported from `packages/adapter-xslate/src/adapter/analysis/component-tree.ts`.
|
|
5
|
+
* Pure functions over the IR — they read no adapter instance state.
|
|
6
|
+
* `collectImportedLoopChildComponentErrors` returns its diagnostics instead
|
|
7
|
+
* of pushing onto the adapter's error list, so the adapter stays the sole
|
|
8
|
+
* owner of `errors`.
|
|
9
|
+
*/
|
|
10
|
+
import type { ComponentIR, CompilerError } from '@barefootjs/jsx';
|
|
11
|
+
/**
|
|
12
|
+
* Whether the component needs the client runtime — it owns reactive state
|
|
13
|
+
* (signals / effects / onMount) or the analyzer flagged it as needing init.
|
|
14
|
+
*/
|
|
15
|
+
export declare function hasClientInteractivity(ir: ComponentIR): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Build a `BF103` diagnostic for every component reference inside a loop body
|
|
18
|
+
* whose name is imported from a relative-path module. Mirror of the Go /
|
|
19
|
+
* Xslate / Jinja2 adapter's check — this adapter has the same
|
|
20
|
+
* cross-template-registration constraint at request time (each `.j2`
|
|
21
|
+
* component file must be registered with the shared `minijinja::Environment`
|
|
22
|
+
* loader alongside the parent). Returns the diagnostics so the caller pushes
|
|
23
|
+
* them onto its own error list.
|
|
24
|
+
*/
|
|
25
|
+
export declare function collectImportedLoopChildComponentErrors(ir: ComponentIR, componentName: string): CompilerError[];
|
|
26
|
+
//# sourceMappingURL=component-tree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-tree.d.ts","sourceRoot":"","sources":["../../../src/adapter/analysis/component-tree.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,WAAW,EAUX,aAAa,EACd,MAAM,iBAAiB,CAAA;AAExB;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,WAAW,GAAG,OAAO,CAO/D;AAED;;;;;;;;GAQG;AACH,wBAAgB,uCAAuC,CACrD,EAAE,EAAE,WAAW,EACf,aAAa,EAAE,MAAM,GACpB,aAAa,EAAE,CAqEjB"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural classifier for JS expressions whose result is a boolean value
|
|
3
|
+
* (or unambiguously stringifies to "true"/"false" in JS).
|
|
4
|
+
*
|
|
5
|
+
* Near-verbatim port of `packages/adapter-jinja/src/adapter/boolean-result.ts`
|
|
6
|
+
* (itself ported from `packages/adapter-xslate/src/adapter/boolean-result.ts`,
|
|
7
|
+
* which was ported from the Mojo adapter's `bf->bool_str` classifier). Used
|
|
8
|
+
* by this adapter for TWO purposes — one inherited from Xslate, one new:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Attribute/text stringification** (inherited): route a boolean-shaped
|
|
11
|
+
* reactive binding through the runtime `bf.bool_str` helper so the
|
|
12
|
+
* serialised value matches JS `String(boolean)` ("true"/"false"). Python
|
|
13
|
+
* has a real `bool` type (unlike Perl's `1`/`''`), but Python's own
|
|
14
|
+
* `str(True)` == `"True"` (capitalised) — still wrong for HTML output —
|
|
15
|
+
* so the same explicit routing is required.
|
|
16
|
+
* 2. **Condition-position truthy wrapping** (new — `isBooleanResultParsed` is
|
|
17
|
+
* exported, not just the string-based `isBooleanResultExpr`): Python
|
|
18
|
+
* truthiness diverges from JS specifically on empty containers (`[]` /
|
|
19
|
+
* `{}` are JS-truthy, Python-falsy). Perl doesn't have this problem — a
|
|
20
|
+
* Perl array/hash REFERENCE is always true, matching JS objects/arrays
|
|
21
|
+
* being unconditionally truthy — which is why Xslate never needed a
|
|
22
|
+
* truthy-routing layer for `if`/ternary/`&&`/`||` conditions. The Jinja
|
|
23
|
+
* adapter's condition-emission call sites (see `minijinja-adapter.ts`'s
|
|
24
|
+
* `convertConditionToJinja`) reuse this SAME structural classifier: a
|
|
25
|
+
* condition that is already unambiguously boolean-shaped emits directly;
|
|
26
|
+
* everything else is wrapped in `bf.truthy(...)` (a JS-faithful
|
|
27
|
+
* `ToBoolean`) before being used as an `{% if %}` / ternary test.
|
|
28
|
+
*
|
|
29
|
+
* The classifier walks a `ParsedExpr` produced by
|
|
30
|
+
* `@barefootjs/jsx::parseExpression` — same AST the filter / loop lowerings
|
|
31
|
+
* already use — so detection is structural rather than regex-text-matching.
|
|
32
|
+
* Wrapped expression text is left to the caller's existing
|
|
33
|
+
* `convertExpressionToJinja` pipeline; this module only decides whether to
|
|
34
|
+
* wrap.
|
|
35
|
+
*
|
|
36
|
+
* Detected shapes:
|
|
37
|
+
* - `binary` with a comparison operator (`<`, `>`, `<=`, `>=`, `==`, `===`,
|
|
38
|
+
* `!=`, `!==`)
|
|
39
|
+
* - `unary` with logical `!`
|
|
40
|
+
* - `literal` with `literalType: 'boolean'`
|
|
41
|
+
* - `logical` (`&&` / `||` / `??`) when both sides are themselves
|
|
42
|
+
* boolean-result (catches `x > 0 && y < 10`; intentionally does NOT
|
|
43
|
+
* catch `x() || 'fallback'` whose right side stringifies as a regular
|
|
44
|
+
* value)
|
|
45
|
+
* - `conditional` (`?:`) when both branches are themselves boolean-result
|
|
46
|
+
*
|
|
47
|
+
* Anything else returns `false` — including bare identifiers (`accepted`)
|
|
48
|
+
* and call expressions (`accepted()`) whose return type the adapter has no
|
|
49
|
+
* way to infer from source text alone.
|
|
50
|
+
*/
|
|
51
|
+
import { type ParsedExpr } from '@barefootjs/jsx';
|
|
52
|
+
/**
|
|
53
|
+
* Structural boolean-result check over an already-parsed `ParsedExpr` tree.
|
|
54
|
+
* Exported (unlike Xslate's private equivalent) so the condition-position
|
|
55
|
+
* truthy-wrapping call sites can reuse it without a stringify → re-parse
|
|
56
|
+
* round-trip.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isBooleanResultParsed(node: ParsedExpr): boolean;
|
|
59
|
+
export declare function isBooleanResultExpr(expr: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* True when `expr`'s top-level shape is an explicit JS `String(x)` call
|
|
62
|
+
* (the `EVAL_BUILTIN_IDENTS` builtin the compiler recognizes structurally —
|
|
63
|
+
* `packages/jsx/src/expression-parser.ts`'s `EVAL_BUILTIN_IDENTS`; lowered
|
|
64
|
+
* by this adapter's `String` template primitive to `bf.string(x)`, see
|
|
65
|
+
* `lib/constants.ts`).
|
|
66
|
+
*
|
|
67
|
+
* Guards the `isAriaBooleanAttr`-driven `bf.bool_str(...)` override in
|
|
68
|
+
* `minijinja-adapter.ts`'s `elementAttrEmitter`: `bf.string` and `bf.bool_str`
|
|
69
|
+
* produce IDENTICAL text for a real Python `bool` (both are `"true"` /
|
|
70
|
+
* `"false"`), so applying `bf.bool_str` to `String(x)`'s ALREADY-STRINGIFIED
|
|
71
|
+
* result is not a no-op — it is a Python-truthiness test over that STRING
|
|
72
|
+
* ("false" is a non-empty Python string, hence truthy, so
|
|
73
|
+
* `bf.bool_str(bf.string(false))` would wrongly render `"true"`). The Kolon
|
|
74
|
+
* port has the identical double-wrap shape and "works" only by an
|
|
75
|
+
* unrelated accident (`JSON::PP::Boolean` stringifies to `"0"`/`"1"`, and
|
|
76
|
+
* Perl specifically treats the STRING `"0"` as falsy) that doesn't hold in
|
|
77
|
+
* Python. An author who explicitly writes `String(...)` has already opted
|
|
78
|
+
* into JS `String()` semantics — `bf.string(x)` alone (which DOES special-
|
|
79
|
+
* case booleans, see `runtime.js_string`) is the complete, correct
|
|
80
|
+
* lowering; no attribute-name-driven override should run again on top of
|
|
81
|
+
* it.
|
|
82
|
+
*/
|
|
83
|
+
export declare function isExplicitStringCall(expr: string): boolean;
|
|
84
|
+
export declare function isAriaBooleanAttr(name: string): boolean;
|
|
85
|
+
//# sourceMappingURL=boolean-result.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boolean-result.d.ts","sourceRoot":"","sources":["../../src/adapter/boolean-result.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAalE;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAuB/D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAS1D;AAyCD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract the extracted expression-emitter modules depend on instead of
|
|
3
|
+
* the concrete `MinijinjaAdapter`.
|
|
4
|
+
*
|
|
5
|
+
* Near-verbatim port of `packages/adapter-jinja/src/adapter/emit-context.ts`
|
|
6
|
+
* (itself ported from `packages/adapter-xslate/src/adapter/emit-context.ts`).
|
|
7
|
+
* This adapter's top-level expression lowering is mutually recursive with
|
|
8
|
+
* the adapter's own const/record resolution and its filter-predicate
|
|
9
|
+
* emitter, so the extracted `JinjaTopLevelEmitter` still needs to call back
|
|
10
|
+
* into shared per-compile state and recursive entry points.
|
|
11
|
+
* `JinjaEmitContext` is that seam: the emitter takes a `JinjaEmitContext`
|
|
12
|
+
* built by the adapter's private `emitCtx` getter (the adapter does NOT
|
|
13
|
+
* `implements` this interface, so the wrapped members stay private and off
|
|
14
|
+
* its exported public type). The emitter depends on this narrow interface
|
|
15
|
+
* rather than the full class, so the coupling is explicit and it's
|
|
16
|
+
* unit-testable against a stub.
|
|
17
|
+
*
|
|
18
|
+
* Keep this surface minimal: add a member only when an extracted module
|
|
19
|
+
* genuinely needs it, so the seam documents the real cross-module coupling
|
|
20
|
+
* rather than re-exposing the whole adapter.
|
|
21
|
+
*/
|
|
22
|
+
import type { ParsedExpr, CompilerError, IRMetadata } from '@barefootjs/jsx';
|
|
23
|
+
export interface JinjaEmitContext {
|
|
24
|
+
/**
|
|
25
|
+
* (#1922) Local binding names the request-scoped `searchParams()` env signal
|
|
26
|
+
* is imported under. Non-empty enables the env-signal method-call lowering.
|
|
27
|
+
*/
|
|
28
|
+
readonly _searchParamsLocals: Set<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Inline a module-scope pure string-literal const by name as the resolved
|
|
31
|
+
* literal value, or null when the name is not such a const.
|
|
32
|
+
*/
|
|
33
|
+
_resolveModuleStringConst(name: string): string | null;
|
|
34
|
+
/** Resolve a literal const (`const totalPages = 5`) to its Jinja value, or null. */
|
|
35
|
+
_resolveLiteralConst(name: string): string | null;
|
|
36
|
+
/**
|
|
37
|
+
* Resolve a static property access on a module object-literal const
|
|
38
|
+
* (`variantClasses.ghost`) to its Jinja value at compile time, or null.
|
|
39
|
+
*/
|
|
40
|
+
_resolveStaticRecordLiteral(objectName: string, key: string): string | null;
|
|
41
|
+
/** Record a BF101 unsupported-expression diagnostic. */
|
|
42
|
+
_recordExprBF101(message: string, reason?: string): void;
|
|
43
|
+
/** Lower a filter/predicate body to its Jinja form, bound to `param`. */
|
|
44
|
+
_renderJinjaFilterExprPublic(expr: ParsedExpr, param: string): string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The contract the extracted object-literal / conditional-spread lowering
|
|
48
|
+
* (`spread/spread-codegen.ts`) depends on. Declared separately from
|
|
49
|
+
* `JinjaEmitContext` so each extracted module's real coupling is documented
|
|
50
|
+
* precisely. Mirror of the Xslate adapter's `XslateSpreadContext`.
|
|
51
|
+
*/
|
|
52
|
+
export interface JinjaSpreadContext {
|
|
53
|
+
/** Component name, for diagnostic source locations. */
|
|
54
|
+
readonly componentName: string;
|
|
55
|
+
/** Per-compile diagnostic list the spread lowering appends to. */
|
|
56
|
+
readonly errors: CompilerError[];
|
|
57
|
+
/** Local-constant metadata, for resolving `Record[key]` spread values. */
|
|
58
|
+
readonly localConstants: IRMetadata['localConstants'];
|
|
59
|
+
/** Prop params, for classifying a bare-identifier index as a prop. */
|
|
60
|
+
readonly propsParams: {
|
|
61
|
+
name: string;
|
|
62
|
+
}[];
|
|
63
|
+
/**
|
|
64
|
+
* Lower a JS expression to its Jinja form (the core recursive entry).
|
|
65
|
+
*
|
|
66
|
+
* When the IR already carries a structured `ParsedExpr` tree, pass it as
|
|
67
|
+
* `preParsed` so the converter threads it straight through instead of
|
|
68
|
+
* re-parsing `expr`. With `preParsed` set, `expr` is unused for parsing
|
|
69
|
+
* (the converter derives any diagnostic text from the tree), so callers
|
|
70
|
+
* may pass `''`.
|
|
71
|
+
*/
|
|
72
|
+
convertExpressionToJinja(expr: string, preParsed?: ParsedExpr): string;
|
|
73
|
+
/**
|
|
74
|
+
* Lower a JS expression to a Jinja CONDITION (routes through `bf.truthy`
|
|
75
|
+
* unless the expression is structurally already boolean-shaped — see
|
|
76
|
+
* `boolean-result.ts`). Used for the conditional-spread ternary's test,
|
|
77
|
+
* which is a condition position, not a value position. Same `preParsed`
|
|
78
|
+
* contract as `convertExpressionToJinja`.
|
|
79
|
+
*/
|
|
80
|
+
convertConditionToJinja(expr: string, preParsed?: ParsedExpr): string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The contract the extracted in-template memo / context seeding
|
|
84
|
+
* (`memo/seed.ts`) depends on. The seed lowering recurses into the core
|
|
85
|
+
* expression lowering to compute a derived signal/memo value or a context
|
|
86
|
+
* default; that recursive entry is its only adapter coupling.
|
|
87
|
+
*/
|
|
88
|
+
export interface JinjaMemoContext {
|
|
89
|
+
/**
|
|
90
|
+
* Lower a JS expression to its Jinja form (the core recursive entry). See
|
|
91
|
+
* `JinjaSpreadContext.convertExpressionToJinja` for the `preParsed` contract.
|
|
92
|
+
*/
|
|
93
|
+
convertExpressionToJinja(expr: string, preParsed?: ParsedExpr): string;
|
|
94
|
+
/**
|
|
95
|
+
* Per-compile diagnostic list `convertExpressionToJinja` appends to on an
|
|
96
|
+
* unsupported shape (`_recordExprBF101`). `memo/seed.ts`'s
|
|
97
|
+
* `generateDerivedMemoSeed` is a SPECULATIVE "try this in-template
|
|
98
|
+
* recomputation, else fall back to the static ssrDefault seed" attempt per
|
|
99
|
+
* plan step — unlike every other `convertExpressionToJinja` call site, a
|
|
100
|
+
* failure here must NOT become a hard compile error, so it snapshots this
|
|
101
|
+
* array's length before calling in and truncates back to it on failure
|
|
102
|
+
* (discarding whatever `_recordExprBF101` appended) rather than letting
|
|
103
|
+
* the error escape.
|
|
104
|
+
*/
|
|
105
|
+
readonly errors: CompilerError[];
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=emit-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit-context.d.ts","sourceRoot":"","sources":["../../src/adapter/emit-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;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;;;OAGG;IACH,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;IAEtE;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,CAAA;CACjC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Array / string method lowering for the minijinja template adapter.
|
|
3
|
+
*
|
|
4
|
+
* Ported from `packages/adapter-xslate/src/adapter/expr/array-method.ts`.
|
|
5
|
+
* Pure free functions shared by both the filter-context emitter and the
|
|
6
|
+
* top-level emitter — they take an `emit` callback for receiver / argument
|
|
7
|
+
* recursion and read no adapter instance state.
|
|
8
|
+
*
|
|
9
|
+
* The receiver/array helpers are the same runtime methods the Xslate adapter
|
|
10
|
+
* calls, invoked as `bf.NAME(...)` (bare, no `$` sigil) instead of
|
|
11
|
+
* `$bf.NAME(...)`.
|
|
12
|
+
*/
|
|
13
|
+
import type { ParsedExpr, ArrayMethod, SortComparator, FlatDepth } from '@barefootjs/jsx';
|
|
14
|
+
export declare function renderArrayMethod(method: ArrayMethod, object: ParsedExpr, args: ParsedExpr[], emit: (e: ParsedExpr) => string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Emit a `.sort(cmp)` / `.toSorted(cmp)` via the runtime evaluator (#2018):
|
|
17
|
+
* the comparator body travels as serialized-ParsedExpr JSON, evaluated per
|
|
18
|
+
* comparison against `{paramA, paramB, …captured}`. Returns null when the
|
|
19
|
+
* body is outside the evaluator surface (e.g. a `localeCompare` comparator —
|
|
20
|
+
* `serializeParsedExpr` refuses it), so the caller falls back to the
|
|
21
|
+
* structured `bf.sort`. `params` are the comparator arrow's two params
|
|
22
|
+
* (`[paramA, paramB]`).
|
|
23
|
+
*/
|
|
24
|
+
export declare function renderSortEval(recv: string, body: ParsedExpr, params: string[], emit: (e: ParsedExpr) => string): string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Emit a `.reduce(fn, init)` / `.reduceRight(fn, init)` via the runtime
|
|
27
|
+
* evaluator (#2018): the reducer body travels as serialized-ParsedExpr JSON,
|
|
28
|
+
* folded over the receiver from `init` in `direction` order. `params` are the
|
|
29
|
+
* reducer arrow's params (`[paramAcc, paramItem]`); `init` is the initial-value
|
|
30
|
+
* `ParsedExpr` from the call's trailing argument. Returns null when the body is
|
|
31
|
+
* outside the evaluator surface, or when `init` is not a literal string/number
|
|
32
|
+
* (→ caller refuses with BF101). A numeric seed passes through as a bare
|
|
33
|
+
* Jinja number; a string seed as a single-quoted literal.
|
|
34
|
+
*/
|
|
35
|
+
export declare function renderReduceEval(recv: string, body: ParsedExpr, params: string[], init: ParsedExpr, direction: 'left' | 'right', emit: (e: ParsedExpr) => string): string | null;
|
|
36
|
+
/**
|
|
37
|
+
* Emit a higher-order predicate call via the runtime evaluator (#2018, P2):
|
|
38
|
+
* `bf.filter_eval` / `bf.every_eval` / `bf.some_eval` / `bf.find_eval` /
|
|
39
|
+
* `bf.find_index_eval`, carrying the serialized predicate body + captured env
|
|
40
|
+
* dict. Generalizes the lambda lowering to the same JS-faithful evaluator
|
|
41
|
+
* the Go/Xslate adapters use. Returns null when the predicate is outside the
|
|
42
|
+
* evaluator surface (e.g. a method-call predicate — `serializeParsedExpr`
|
|
43
|
+
* refuses it), so the caller falls back to the lambda form. `forward`
|
|
44
|
+
* (find / findIndex family only) selects the search direction — `false` =
|
|
45
|
+
* findLast / findLastIndex.
|
|
46
|
+
*/
|
|
47
|
+
export declare function renderPredicateEval(funcName: string, recv: string, predicate: ParsedExpr, param: string, emit: (e: ParsedExpr) => string, forward?: boolean): string | null;
|
|
48
|
+
/**
|
|
49
|
+
* Emit a `.flatMap(proj)` via the runtime evaluator (#2018, P3): the projection
|
|
50
|
+
* body serializes to JSON and `bf.flat_map_eval` projects + flattens one
|
|
51
|
+
* level. `param` is the projection arrow's single param. Returns null when the
|
|
52
|
+
* projection is outside the evaluator surface (→ caller refuses with BF101).
|
|
53
|
+
*/
|
|
54
|
+
export declare function renderFlatMapEval(recv: string, body: ParsedExpr, param: string, emit: (e: ParsedExpr) => string): string | null;
|
|
55
|
+
/**
|
|
56
|
+
* Emit a value-producing `.map(cb)` via the runtime evaluator (#2073): the
|
|
57
|
+
* projection body serializes to JSON and `bf.map_eval` projects each element,
|
|
58
|
+
* one result per element (no flatten — the JS `.map` contract). Composes
|
|
59
|
+
* through the array-method chain (`.map(cb).join(' ')`). Returns null when
|
|
60
|
+
* the projection is outside the evaluator surface (→ caller refuses with
|
|
61
|
+
* BF101). The JSX-returning `.map` is an IRLoop upstream and never reaches
|
|
62
|
+
* this emit.
|
|
63
|
+
*/
|
|
64
|
+
export declare function renderMapEval(recv: string, body: ParsedExpr, param: string, emit: (e: ParsedExpr) => string): string | null;
|
|
65
|
+
/**
|
|
66
|
+
* Shared Jinja emit for `.sort(cmp)` / `.toSorted(cmp)`. Used by both the
|
|
67
|
+
* filter-context emitter and the top-level emitter, plus the loop-array
|
|
68
|
+
* wrap in `renderLoop`. The runtime `bf.sort` accepts an opts dict and
|
|
69
|
+
* returns a fresh list.
|
|
70
|
+
*/
|
|
71
|
+
export declare function renderSortMethod(recv: string, c: SortComparator): string;
|
|
72
|
+
export declare function renderFlatMethod(recv: string, depth: FlatDepth | {
|
|
73
|
+
expr: ParsedExpr;
|
|
74
|
+
}, emit: (e: ParsedExpr) => string): string;
|
|
75
|
+
//# sourceMappingURL=array-method.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"array-method.d.ts","sourceRoot":"","sources":["../../../src/adapter/expr/array-method.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,cAAc,EACd,SAAS,EACV,MAAM,iBAAiB,CAAA;AAGxB,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,UAAU,EAAE,EAClB,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAC9B,MAAM,CA6HR;AAqBD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAC9B,MAAM,GAAG,IAAI,CAUf;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,MAAM,GAAG,OAAO,EAC3B,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAC9B,MAAM,GAAG,IAAI,CAkBf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,UAAU,EACrB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,EAC/B,OAAO,CAAC,EAAE,OAAO,GAChB,MAAM,GAAG,IAAI,CAMf;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAC9B,MAAM,GAAG,IAAI,CAKf;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAC9B,MAAM,GAAG,IAAI,CAKf;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,cAAc,GAAG,MAAM,CASxE;AAGD,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,SAAS,GAAG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,EACvC,IAAI,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,MAAM,GAC9B,MAAM,CAmBR"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ParsedExpr → Jinja2 emitters for the minijinja template adapter.
|
|
3
|
+
*
|
|
4
|
+
* Ported from `packages/adapter-xslate/src/adapter/expr/emitters.ts`
|
|
5
|
+
* (`XslateFilterEmitter` / `XslateTopLevelEmitter`). Two `ParsedExprEmitter`
|
|
6
|
+
* implementations:
|
|
7
|
+
*
|
|
8
|
+
* - `JinjaFilterEmitter` — filter/predicate context (loop param + local
|
|
9
|
+
* aliases + bare identifier signal fallback); self-contained, reads no
|
|
10
|
+
* adapter state.
|
|
11
|
+
* - `JinjaTopLevelEmitter` — top-level / per-render-var context; depends on
|
|
12
|
+
* the adapter only through the narrow `JinjaEmitContext` seam.
|
|
13
|
+
*
|
|
14
|
+
* Two deliberate divergences from the Kolon port, both documented at their
|
|
15
|
+
* definition site below:
|
|
16
|
+
*
|
|
17
|
+
* 1. **JS-truthy condition wrapping** (`truthyTest`) — Python truthiness
|
|
18
|
+
* diverges from JS on empty containers; Perl doesn't have this problem
|
|
19
|
+
* (a Perl ref is always true), so Kolon never needed this. Every
|
|
20
|
+
* condition-TEST position (`!x`, the left operand of `&&`/`||`, a
|
|
21
|
+
* ternary's test) routes through the shared `bf.truthy(...)` runtime
|
|
22
|
+
* helper unless the operand is structurally already boolean-shaped
|
|
23
|
+
* (`isBooleanResultParsed`). `&&`/`||` still return the ORIGINAL
|
|
24
|
+
* operand VALUE (not a coerced bool) on the taken branch — matching JS
|
|
25
|
+
* `a || b` returning `a` itself, not `true` — only the BRANCH TEST uses
|
|
26
|
+
* `bf.truthy`. Because Jinja's `if/else` ternary has no way to reuse an
|
|
27
|
+
* already-computed test value for both the test and (conditionally) the
|
|
28
|
+
* result, the left operand's rendered text is emitted TWICE (once as
|
|
29
|
+
* the test, once as the value) — safe because every operand reaching
|
|
30
|
+
* this pipeline is a pure, side-effect-free read (a signal/prop lookup
|
|
31
|
+
* or a call into a pure runtime helper), never a stateful expression.
|
|
32
|
+
* 2. **No Jinja lambda for the predicate-callback fallback.** Kolon's
|
|
33
|
+
* `-> $x { … }` lambda is the Xslate top-level emitter's fallback when a
|
|
34
|
+
* `.filter`/`.every`/`.some`/`.find*` predicate can't be serialized to
|
|
35
|
+
* the runtime evaluator's JSON form (e.g. a nested method-call
|
|
36
|
+
* predicate). Jinja has no lambda-expression syntax at all, so there is
|
|
37
|
+
* no equivalent fallback to port. `JinjaTopLevelEmitter` therefore uses
|
|
38
|
+
* ONE mechanism for every higher-order callback: the evaluator-JSON
|
|
39
|
+
* `*_eval` payload (`bf.filter_eval`, `bf.sort_eval`, …). When
|
|
40
|
+
* `serializeParsedExpr` refuses the body, the call surfaces `BF101`
|
|
41
|
+
* instead of silently degrading — `.sort`/`.toSorted` is the one
|
|
42
|
+
* exception, whose non-lambda STRUCTURED fallback (`bf.sort` with a
|
|
43
|
+
* `{keys: […]}` descriptor, no callback body at all) survives the port
|
|
44
|
+
* unchanged since it was never lambda-shaped to begin with.
|
|
45
|
+
* `JinjaFilterEmitter` (the loop `.filter().map()` INLINE predicate,
|
|
46
|
+
* rendered as a plain boolean expression, never a lambda) is otherwise
|
|
47
|
+
* unaffected and still used for that path plus the filter-predicate
|
|
48
|
+
* entry point `_renderJinjaFilterExprPublic`.
|
|
49
|
+
*/
|
|
50
|
+
import { type ParsedExprEmitter, type ArrayMethod, type LiteralType, type ParsedExpr, type ObjectLiteralProperty, type FlatDepth, type TemplatePart } from '@barefootjs/jsx';
|
|
51
|
+
import type { JinjaEmitContext } from '../emit-context.ts';
|
|
52
|
+
/**
|
|
53
|
+
* Route a condition-TEST position through `bf.truthy(...)` unless the node
|
|
54
|
+
* is structurally already boolean-shaped. See the file header (divergence
|
|
55
|
+
* 1). Shared by both emitters below and reused by the adapter's top-level
|
|
56
|
+
* `convertConditionToJinja` for IR-level `if` / loop-filter conditions.
|
|
57
|
+
*/
|
|
58
|
+
export declare function truthyTest(node: ParsedExpr, rendered: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Lowering for the predicate body of a filter / every / some / find, plus the
|
|
61
|
+
* same shape used by the loop-hoist `.filter().map()` inline condition.
|
|
62
|
+
* Higher-order predicates are emitted using Jinja's own scalar comparison
|
|
63
|
+
* operators.
|
|
64
|
+
*
|
|
65
|
+
* NOTE: Jinja has no `[x for x in … if …]`-as-expression form usable inline
|
|
66
|
+
* here (a comprehension is a value producer, not a boolean test), so a
|
|
67
|
+
* nested higher-order call (`x.tags.filter(...)`, `other.some(...)`) inside
|
|
68
|
+
* a predicate has no faithful scalar lowering here either — same BF101
|
|
69
|
+
* surfacing as Kolon (#2038) instead of silently degrading to the
|
|
70
|
+
* callback's receiver.
|
|
71
|
+
*/
|
|
72
|
+
export declare class JinjaFilterEmitter implements ParsedExprEmitter {
|
|
73
|
+
private readonly param;
|
|
74
|
+
private readonly localVarMap;
|
|
75
|
+
private readonly isStringName;
|
|
76
|
+
private readonly onUnsupported?;
|
|
77
|
+
constructor(param: string, localVarMap: Map<string, string>, isStringName?: (n: string) => boolean, onUnsupported?: ((message: string, reason?: string) => void) | undefined);
|
|
78
|
+
identifier(name: string): string;
|
|
79
|
+
literal(value: string | number | boolean | null, literalType: LiteralType): string;
|
|
80
|
+
member(object: ParsedExpr, property: string, _computed: boolean, emit: (e: ParsedExpr) => string): string;
|
|
81
|
+
indexAccess(object: ParsedExpr, index: ParsedExpr, emit: (e: ParsedExpr) => string): string;
|
|
82
|
+
call(callee: ParsedExpr, args: ParsedExpr[], emit: (e: ParsedExpr) => string): string;
|
|
83
|
+
unary(op: string, argument: ParsedExpr, emit: (e: ParsedExpr) => string): string;
|
|
84
|
+
binary(op: string, left: ParsedExpr, right: ParsedExpr, emit: (e: ParsedExpr) => string): string;
|
|
85
|
+
logical(op: '&&' | '||' | '??', left: ParsedExpr, right: ParsedExpr, emit: (e: ParsedExpr) => string): string;
|
|
86
|
+
callbackMethod(method: string, object: ParsedExpr, _arrow: Extract<ParsedExpr, {
|
|
87
|
+
kind: 'arrow';
|
|
88
|
+
}>, _restArgs: ParsedExpr[], emit: (e: ParsedExpr) => string): string;
|
|
89
|
+
arrayLiteral(elements: ParsedExpr[], emit: (e: ParsedExpr) => string): string;
|
|
90
|
+
arrayMethod(method: ArrayMethod, object: ParsedExpr, args: ParsedExpr[], emit: (e: ParsedExpr) => string): string;
|
|
91
|
+
flatMethod(object: ParsedExpr, depth: FlatDepth | {
|
|
92
|
+
expr: ParsedExpr;
|
|
93
|
+
}, emit: (e: ParsedExpr) => string): string;
|
|
94
|
+
conditional(_test: ParsedExpr, _consequent: ParsedExpr, _alternate: ParsedExpr): string;
|
|
95
|
+
templateLiteral(_parts: TemplatePart[]): string;
|
|
96
|
+
arrow(_params: string[], _body: ParsedExpr): string;
|
|
97
|
+
regex(_raw: string): string;
|
|
98
|
+
unsupported(_raw: string, _reason: string): string;
|
|
99
|
+
objectLiteral(_properties: ObjectLiteralProperty[], _raw: string, _emit: (e: ParsedExpr) => string): string;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Lowering for top-level expressions whose identifiers resolve against the
|
|
103
|
+
* Jinja template's per-render context vars (signals, props, locals
|
|
104
|
+
* introduced by `{% set x = … %}`). Differs from the filter emitter mainly
|
|
105
|
+
* in
|
|
106
|
+
* - `conditional` is supported (filter predicates can't return ternaries),
|
|
107
|
+
* - higher-order methods route through `bf.*` array/evaluator helpers,
|
|
108
|
+
* - no lambda fallback exists (see the file header, divergence 2).
|
|
109
|
+
*/
|
|
110
|
+
export declare class JinjaTopLevelEmitter implements ParsedExprEmitter {
|
|
111
|
+
private readonly ctx;
|
|
112
|
+
constructor(ctx: JinjaEmitContext);
|
|
113
|
+
identifier(name: string): string;
|
|
114
|
+
literal(value: string | number | boolean | null, literalType: LiteralType): string;
|
|
115
|
+
member(object: ParsedExpr, property: string, _computed: boolean, emit: (e: ParsedExpr) => string): string;
|
|
116
|
+
indexAccess(object: ParsedExpr, index: ParsedExpr, emit: (e: ParsedExpr) => string): string;
|
|
117
|
+
call(callee: ParsedExpr, args: ParsedExpr[], emit: (e: ParsedExpr) => string): string;
|
|
118
|
+
unary(op: string, argument: ParsedExpr, emit: (e: ParsedExpr) => string): string;
|
|
119
|
+
binary(op: string, left: ParsedExpr, right: ParsedExpr, emit: (e: ParsedExpr) => string): string;
|
|
120
|
+
logical(op: '&&' | '||' | '??', left: ParsedExpr, right: ParsedExpr, emit: (e: ParsedExpr) => string): string;
|
|
121
|
+
callbackMethod(method: string, object: ParsedExpr, arrow: Extract<ParsedExpr, {
|
|
122
|
+
kind: 'arrow';
|
|
123
|
+
}>, restArgs: ParsedExpr[], emit: (e: ParsedExpr) => string): string;
|
|
124
|
+
/**
|
|
125
|
+
* Lower a boolean-predicate callback (`filter` / `find*` / `every` /
|
|
126
|
+
* `some`). See the file header, divergence 2: Jinja has no lambda
|
|
127
|
+
* expression, so — unlike Kolon — there is no non-evaluator fallback here.
|
|
128
|
+
* A predicate the evaluator can't model surfaces `BF101`.
|
|
129
|
+
*/
|
|
130
|
+
private _emitPredicateCallback;
|
|
131
|
+
arrayLiteral(elements: ParsedExpr[], emit: (e: ParsedExpr) => string): string;
|
|
132
|
+
arrayMethod(method: ArrayMethod, object: ParsedExpr, args: ParsedExpr[], emit: (e: ParsedExpr) => string): string;
|
|
133
|
+
flatMethod(object: ParsedExpr, depth: FlatDepth | {
|
|
134
|
+
expr: ParsedExpr;
|
|
135
|
+
}, emit: (e: ParsedExpr) => string): string;
|
|
136
|
+
conditional(test: ParsedExpr, consequent: ParsedExpr, alternate: ParsedExpr, emit: (e: ParsedExpr) => string): string;
|
|
137
|
+
templateLiteral(parts: TemplatePart[], emit: (e: ParsedExpr) => string): string;
|
|
138
|
+
arrow(_params: string[], _body: ParsedExpr): string;
|
|
139
|
+
regex(_raw: string): string;
|
|
140
|
+
unsupported(_raw: string, _reason: string): string;
|
|
141
|
+
objectLiteral(properties: ObjectLiteralProperty[], _raw: string, _emit: (e: ParsedExpr) => string): string;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=emitters.d.ts.map
|