@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
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
//! Port of `packages/adapter-jinja/python/barefootjs/search_params.py`
|
|
2
|
+
//! (itself a port of `packages/adapter-perl/lib/BarefootJS/SearchParams.pm`).
|
|
3
|
+
//!
|
|
4
|
+
//! Request-scoped SSR view of the query string behind the reactive
|
|
5
|
+
//! `searchParams()` environment signal. The framework integration builds
|
|
6
|
+
//! one per request from the request URL and threads it into the template
|
|
7
|
+
//! scope as `searchParams` (the camelCase JS name the adapters keep, like
|
|
8
|
+
//! every other signal/prop var); the compiled template reads it via
|
|
9
|
+
//! `{{ searchParams.get('key') }}` -- so this is exposed to minijinja
|
|
10
|
+
//! templates as a [`minijinja::value::Object`] (implemented below) whose
|
|
11
|
+
//! `.get(key)` method call dispatches to [`SearchParams::get`].
|
|
12
|
+
//!
|
|
13
|
+
//! Semantics mirror the browser's `URLSearchParams.get` exactly under the
|
|
14
|
+
//! adapters' `?? -> or` lowering: `get()` returns the first value for a
|
|
15
|
+
//! key, or `None`/absent when the key is absent. The minijinja lowering of
|
|
16
|
+
//! `??` should coalesce only an absent/none result (a bare `or` would ALSO
|
|
17
|
+
//! coalesce a present-but-empty string, which is wrong), preserving the
|
|
18
|
+
//! distinction JS `??` draws between `null` and `''` -- the `Object` impl
|
|
19
|
+
//! below returns `Value::UNDEFINED` (not `Value::from(())`) for an absent
|
|
20
|
+
//! key so the emitted `(x if (x is defined and x is not none) else d)`
|
|
21
|
+
//! lowering falls through to the default exactly once.
|
|
22
|
+
|
|
23
|
+
use minijinja::value::{Enumerator, Object, Value as MjValue};
|
|
24
|
+
use minijinja::{Error, ErrorKind, State};
|
|
25
|
+
use std::collections::HashMap;
|
|
26
|
+
use std::sync::Arc;
|
|
27
|
+
|
|
28
|
+
/// Percent/`+`-decode a query-string component, mirroring
|
|
29
|
+
/// `URLSearchParams`'s `application/x-www-form-urlencoded` parsing. Never
|
|
30
|
+
/// panics on malformed input (lenient parsing, matching the browser).
|
|
31
|
+
///
|
|
32
|
+
/// Operates byte-wise (not char-wise): percent-escapes are always ASCII, so
|
|
33
|
+
/// copying every non-escape byte verbatim keeps multi-byte UTF-8 sequences
|
|
34
|
+
/// intact while still recognizing `%XX` triples.
|
|
35
|
+
fn decode(s: &str) -> String {
|
|
36
|
+
let bytes = s.replace('+', " ").into_bytes();
|
|
37
|
+
let mut raw: Vec<u8> = Vec::with_capacity(bytes.len());
|
|
38
|
+
let mut i = 0usize;
|
|
39
|
+
while i < bytes.len() {
|
|
40
|
+
if bytes[i] == b'%' && i + 2 < bytes.len() {
|
|
41
|
+
let (a, b) = (bytes[i + 1], bytes[i + 2]);
|
|
42
|
+
if (a as char).is_ascii_hexdigit() && (b as char).is_ascii_hexdigit() {
|
|
43
|
+
let hex = std::str::from_utf8(&bytes[i + 1..i + 3]).unwrap_or("00");
|
|
44
|
+
if let Ok(byte) = u8::from_str_radix(hex, 16) {
|
|
45
|
+
raw.push(byte);
|
|
46
|
+
i += 3;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
raw.push(bytes[i]);
|
|
52
|
+
i += 1;
|
|
53
|
+
}
|
|
54
|
+
// Lenient: a byte run that isn't valid UTF-8 is kept (with replacement
|
|
55
|
+
// characters for the invalid bytes) rather than raising -- mirrors
|
|
56
|
+
// Perl's `utf8::decode`, which never dies.
|
|
57
|
+
String::from_utf8(raw).unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#[derive(Debug)]
|
|
61
|
+
pub struct SearchParams {
|
|
62
|
+
values: HashMap<String, Vec<String>>,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
impl SearchParams {
|
|
66
|
+
/// Parse a raw query string into the reader. A leading '?' is
|
|
67
|
+
/// tolerated, '+' decodes to a space, and %XX escapes are decoded.
|
|
68
|
+
pub fn new(query: &str) -> SearchParams {
|
|
69
|
+
let query = query.strip_prefix('?').unwrap_or(query);
|
|
70
|
+
let mut values: HashMap<String, Vec<String>> = HashMap::new();
|
|
71
|
+
for pair in query.split(['&', ';']) {
|
|
72
|
+
if pair.is_empty() {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
let (key, val) = match pair.split_once('=') {
|
|
76
|
+
Some((k, v)) => (k, Some(v)),
|
|
77
|
+
None => (pair, None),
|
|
78
|
+
};
|
|
79
|
+
let key = decode(key);
|
|
80
|
+
let val_decoded = val.map(decode).unwrap_or_default();
|
|
81
|
+
values.entry(key).or_default().push(val_decoded);
|
|
82
|
+
}
|
|
83
|
+
SearchParams { values }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// First value for `key`, or `None` when the key is absent. A
|
|
87
|
+
/// present-but-empty value returns `Some("")`.
|
|
88
|
+
pub fn get(&self, key: &str) -> Option<&str> {
|
|
89
|
+
self.values.get(key).and_then(|v| v.first()).map(|s| s.as_str())
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Wrap as a template-facing `minijinja::value::Value` (`from_object`
|
|
93
|
+
/// wraps in its own internal `Arc`, matching the `self: &Arc<Self>`
|
|
94
|
+
/// convention the `Object` trait's methods below use).
|
|
95
|
+
pub fn to_value(self) -> MjValue {
|
|
96
|
+
MjValue::from_object(self)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
impl Object for SearchParams {
|
|
101
|
+
fn call_method(
|
|
102
|
+
self: &Arc<Self>,
|
|
103
|
+
_state: &State<'_, '_>,
|
|
104
|
+
method: &str,
|
|
105
|
+
args: &[MjValue],
|
|
106
|
+
) -> Result<MjValue, Error> {
|
|
107
|
+
if method != "get" {
|
|
108
|
+
return Err(Error::from(ErrorKind::UnknownMethod));
|
|
109
|
+
}
|
|
110
|
+
let key = args
|
|
111
|
+
.first()
|
|
112
|
+
.and_then(|v| v.as_str())
|
|
113
|
+
.ok_or_else(|| Error::new(ErrorKind::MissingArgument, "get() requires a key"))?;
|
|
114
|
+
match self.get(key) {
|
|
115
|
+
// Present-but-empty stays a real (empty) string so `?? d` does
|
|
116
|
+
// NOT fall back to the default -- only an absent key does.
|
|
117
|
+
Some(v) => Ok(MjValue::from(v)),
|
|
118
|
+
None => Ok(MjValue::UNDEFINED),
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fn enumerate(self: &Arc<Self>) -> Enumerator {
|
|
123
|
+
Enumerator::NonEnumerable
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#[cfg(test)]
|
|
128
|
+
mod tests {
|
|
129
|
+
use super::*;
|
|
130
|
+
|
|
131
|
+
#[test]
|
|
132
|
+
fn lazy_factory_and_get() {
|
|
133
|
+
let sp = SearchParams::new("sort=price");
|
|
134
|
+
assert_eq!(sp.get("sort"), Some("price"));
|
|
135
|
+
let empty = SearchParams::new("");
|
|
136
|
+
assert_eq!(empty.get("anything"), None);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#[test]
|
|
140
|
+
fn none_composition_coalesces_only_none() {
|
|
141
|
+
let absent = SearchParams::new("other=x");
|
|
142
|
+
assert_eq!(absent.get("sort"), None);
|
|
143
|
+
|
|
144
|
+
let empty = SearchParams::new("sort=");
|
|
145
|
+
assert_eq!(empty.get("sort"), Some(""));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#[test]
|
|
149
|
+
fn utf8_percent_decoding() {
|
|
150
|
+
let sp = SearchParams::new("q=%E2%9C%93");
|
|
151
|
+
assert_eq!(sp.get("q"), Some("\u{2713}"));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#[test]
|
|
155
|
+
fn lenient_parsing_never_panics() {
|
|
156
|
+
let _ = SearchParams::new("");
|
|
157
|
+
assert_eq!(SearchParams::new("&&&").get("x"), None);
|
|
158
|
+
assert_eq!(SearchParams::new("=novalue").get("x"), None);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#[test]
|
|
162
|
+
fn plus_decodes_to_space() {
|
|
163
|
+
let sp = SearchParams::new("q=a+b");
|
|
164
|
+
assert_eq!(sp.get("q"), Some("a b"));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#[test]
|
|
168
|
+
fn leading_question_mark_tolerated() {
|
|
169
|
+
let sp = SearchParams::new("?a=1&b=2");
|
|
170
|
+
assert_eq!(sp.get("a"), Some("1"));
|
|
171
|
+
assert_eq!(sp.get("b"), Some("2"));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//! Golden ParsedExpr-evaluator vectors, ported from
|
|
2
|
+
//! `packages/adapter-jinja/python/tests/test_eval_vectors.py` (itself a
|
|
3
|
+
//! port of `packages/adapter-perl/t/eval_vectors.t`).
|
|
4
|
+
//!
|
|
5
|
+
//! Runs `packages/adapter-tests/vectors/eval-vectors.json` --
|
|
6
|
+
//! generated from the JS reference evaluator, shared with the Go and Perl
|
|
7
|
+
//! evaluators -- against `barefootjs::evaluator::evaluate`. The evaluator
|
|
8
|
+
//! is JS-faithful by contract, so unlike the helper vectors there are NO
|
|
9
|
+
//! Rust-side divergences here: each case's real ParsedExpr tree, evaluated
|
|
10
|
+
//! against its environment, must reproduce the JS-computed expect exactly.
|
|
11
|
+
|
|
12
|
+
use barefootjs::evaluator::{self, Env};
|
|
13
|
+
use barefootjs::num::JsValue;
|
|
14
|
+
use serde_json::Value as JsonValue;
|
|
15
|
+
use std::path::PathBuf;
|
|
16
|
+
|
|
17
|
+
fn vectors_path() -> PathBuf {
|
|
18
|
+
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../adapter-tests/vectors/eval-vectors.json")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Spec value-compat comparison -- non-finite sentinel hashes, booleans by
|
|
22
|
+
/// TYPE (the evaluator result must ITSELF be a real bool, not a truthy
|
|
23
|
+
/// number), numbers numerically, arrays/objects recursively, strings by
|
|
24
|
+
/// equality.
|
|
25
|
+
fn matches(got: &JsValue, expect: &JsonValue) -> bool {
|
|
26
|
+
if expect.is_null() {
|
|
27
|
+
return matches!(got, JsValue::Null);
|
|
28
|
+
}
|
|
29
|
+
if let Some(kind) = expect.get("$num").and_then(|v| v.as_str()) {
|
|
30
|
+
return match got {
|
|
31
|
+
JsValue::Number(g) => match kind {
|
|
32
|
+
"NaN" => g.is_nan(),
|
|
33
|
+
"Infinity" => *g == f64::INFINITY,
|
|
34
|
+
"-Infinity" => *g == f64::NEG_INFINITY,
|
|
35
|
+
_ => false,
|
|
36
|
+
},
|
|
37
|
+
_ => false,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if let Some(b) = expect.as_bool() {
|
|
41
|
+
return matches!(got, JsValue::Bool(g) if *g == b);
|
|
42
|
+
}
|
|
43
|
+
if let Some(arr) = expect.as_array() {
|
|
44
|
+
return match got {
|
|
45
|
+
JsValue::Array(g) => g.len() == arr.len() && g.iter().zip(arr).all(|(g, e)| matches(g, e)),
|
|
46
|
+
_ => false,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if let Some(obj) = expect.as_object() {
|
|
50
|
+
return match got {
|
|
51
|
+
JsValue::Object(g) => g.len() == obj.len() && obj.iter().all(|(k, v)| g.get(k).map(|gv| matches(gv, v)).unwrap_or(false)),
|
|
52
|
+
_ => false,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// Numeric comparison only when BOTH are real numbers (not
|
|
56
|
+
// numeric-looking strings) -- e.g. String(42) must return the string
|
|
57
|
+
// "42", and evaluating it as the number 42 must NOT pass.
|
|
58
|
+
if let Some(n) = expect.as_f64() {
|
|
59
|
+
return matches!(got, JsValue::Number(g) if *g == n);
|
|
60
|
+
}
|
|
61
|
+
if let Some(s) = expect.as_str() {
|
|
62
|
+
return matches!(got, JsValue::String(g) if g == s);
|
|
63
|
+
}
|
|
64
|
+
false
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#[test]
|
|
68
|
+
fn eval_vectors_match_js_reference() {
|
|
69
|
+
let path = vectors_path();
|
|
70
|
+
if !path.exists() {
|
|
71
|
+
eprintln!("skipping: eval vectors not available outside the monorepo checkout ({path:?})");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
let doc: JsonValue = serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
|
|
75
|
+
let cases = doc["cases"].as_array().expect("eval-vectors.json has no cases");
|
|
76
|
+
assert!(!cases.is_empty(), "eval-vectors.json contains no cases");
|
|
77
|
+
|
|
78
|
+
let mut failures = Vec::new();
|
|
79
|
+
for case in cases {
|
|
80
|
+
let note = case["note"].as_str().unwrap_or("<unnamed>");
|
|
81
|
+
let expr = &case["expr"];
|
|
82
|
+
let expect = &case["expect"];
|
|
83
|
+
let env: Env = case["env"]
|
|
84
|
+
.as_object()
|
|
85
|
+
.map(|m| m.iter().map(|(k, v)| (k.clone(), JsValue::from_json(v))).collect())
|
|
86
|
+
.unwrap_or_default();
|
|
87
|
+
|
|
88
|
+
let got = evaluator::evaluate(expr, &env);
|
|
89
|
+
if !matches(&got, expect) {
|
|
90
|
+
failures.push(format!("{note}: got {got:?}, want {expect:?}"));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
assert!(failures.is_empty(), "\n{}", failures.join("\n"));
|
|
94
|
+
}
|