@barefootjs/rust 0.20.0 → 0.21.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/index.js +1 -1
- package/dist/adapter/minijinja-adapter.d.ts.map +1 -1
- package/dist/build.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/runtime/src/date.rs +196 -0
- package/runtime/src/evaluator.rs +16 -0
- package/runtime/src/lib.rs +1 -0
- package/runtime/src/num.rs +13 -0
- package/runtime/src/runtime.rs +69 -3
- package/runtime/tests/helper_vectors.rs +33 -1
- package/src/__tests__/minijinja-adapter-unit.test.ts +25 -0
- package/src/adapter/minijinja-adapter.ts +4 -1
- package/src/test-render.ts +9 -0
package/dist/adapter/index.js
CHANGED
|
@@ -188814,7 +188814,7 @@ ${alternate}
|
|
|
188814
188814
|
renderFragment(fragment) {
|
|
188815
188815
|
const children = this.renderChildren(fragment.children);
|
|
188816
188816
|
if (fragment.needsScopeComment) {
|
|
188817
|
-
return `{{ bf.scope_comment() | safe }}${children}`;
|
|
188817
|
+
return `{{ bf.scope_comment() | safe }}${children}{{ bf.scope_comment_end() | safe }}`;
|
|
188818
188818
|
}
|
|
188819
188819
|
return children;
|
|
188820
188820
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"minijinja-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/minijinja-adapter.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"minijinja-adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/minijinja-adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/build.js
CHANGED
|
@@ -188814,7 +188814,7 @@ ${alternate}
|
|
|
188814
188814
|
renderFragment(fragment) {
|
|
188815
188815
|
const children = this.renderChildren(fragment.children);
|
|
188816
188816
|
if (fragment.needsScopeComment) {
|
|
188817
|
-
return `{{ bf.scope_comment() | safe }}${children}`;
|
|
188817
|
+
return `{{ bf.scope_comment() | safe }}${children}{{ bf.scope_comment_end() | safe }}`;
|
|
188818
188818
|
}
|
|
188819
188819
|
return children;
|
|
188820
188820
|
}
|
package/dist/index.js
CHANGED
|
@@ -188814,7 +188814,7 @@ ${alternate}
|
|
|
188814
188814
|
renderFragment(fragment) {
|
|
188815
188815
|
const children = this.renderChildren(fragment.children);
|
|
188816
188816
|
if (fragment.needsScopeComment) {
|
|
188817
|
-
return `{{ bf.scope_comment() | safe }}${children}`;
|
|
188817
|
+
return `{{ bf.scope_comment() | safe }}${children}{{ bf.scope_comment_end() | safe }}`;
|
|
188818
188818
|
}
|
|
188819
188819
|
return children;
|
|
188820
188820
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/rust",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.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,14 +54,14 @@
|
|
|
54
54
|
"directory": "packages/adapter-rust"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@barefootjs/shared": "0.
|
|
57
|
+
"@barefootjs/shared": "0.21.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"@barefootjs/jsx": ">=0.2.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@barefootjs/adapter-tests": "0.1.0",
|
|
64
|
-
"@barefootjs/jsx": "0.
|
|
64
|
+
"@barefootjs/jsx": "0.21.0",
|
|
65
65
|
"typescript": "^5.0.0"
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
//! `date(recv, op)` -- zero-arg `Date.prototype` method lowering
|
|
2
|
+
//! (spec/template-helpers.md "date", #2274). `recv` is either this
|
|
3
|
+
//! runtime's own [`JsValue::Date`] (an epoch-millisecond `i64`) or an
|
|
4
|
+
//! ISO-8601 `String` (a template prop may carry either depending on how
|
|
5
|
+
//! the host populated it) -- both normalize to a single epoch-ms integer
|
|
6
|
+
//! via [`epoch_ms_of`] before dispatch.
|
|
7
|
+
//!
|
|
8
|
+
//! No `chrono` (or any other date crate) is a dependency of this crate --
|
|
9
|
+
//! see the design doc's fixed dependency list, the same constraint
|
|
10
|
+
//! `num.rs`'s numeric-string grammar docstring cites for avoiding `regex`.
|
|
11
|
+
//! Calendar <-> day-count conversion is Howard Hinnant's `civil_from_days`
|
|
12
|
+
//! / `days_from_civil` (public-domain algorithm,
|
|
13
|
+
//! <http://howardhinnant.github.io/date_algorithms.html>), correct for the
|
|
14
|
+
//! full proleptic-Gregorian range this catalogue's vectors exercise
|
|
15
|
+
//! (epoch 0, a pre-1970 instant, a leap day, and a four-digit-year
|
|
16
|
+
//! boundary). Every division that must floor (not truncate) toward
|
|
17
|
+
//! -Infinity for a negative dividend uses `div_euclid`/`rem_euclid`
|
|
18
|
+
//! explicitly rather than plain `/`/`%` (which truncate toward zero in
|
|
19
|
+
//! Rust) -- the one correctness trap this port must not reproduce.
|
|
20
|
+
|
|
21
|
+
use crate::num::JsValue;
|
|
22
|
+
|
|
23
|
+
const MS_PER_DAY: i64 = 86_400_000;
|
|
24
|
+
|
|
25
|
+
/// Hinnant `days_from_civil`: (proleptic-Gregorian y/m/d) -> days since the
|
|
26
|
+
/// Unix epoch (1970-01-01). `m` is 1-based (January = 1).
|
|
27
|
+
fn days_from_civil(y: i64, m: i64, d: i64) -> i64 {
|
|
28
|
+
let y = if m <= 2 { y - 1 } else { y };
|
|
29
|
+
let era = y.div_euclid(400);
|
|
30
|
+
let yoe = y.rem_euclid(400); // [0, 399]
|
|
31
|
+
let mp = if m > 2 { m - 3 } else { m + 9 }; // [0, 11], Mar=0 .. Feb=11
|
|
32
|
+
let doy = (153 * mp + 2).div_euclid(5) + d - 1; // [0, 365]
|
|
33
|
+
let doe = yoe * 365 + yoe.div_euclid(4) - yoe.div_euclid(100) + doy; // [0, 146096]
|
|
34
|
+
era * 146_097 + doe - 719_468
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Hinnant `civil_from_days`: days since the Unix epoch -> (y, m, d), `m`
|
|
38
|
+
/// 1-based. Inverse of [`days_from_civil`].
|
|
39
|
+
fn civil_from_days(z: i64) -> (i64, i64, i64) {
|
|
40
|
+
let z = z + 719_468;
|
|
41
|
+
let era = z.div_euclid(146_097);
|
|
42
|
+
let doe = z.rem_euclid(146_097); // [0, 146096], always non-negative
|
|
43
|
+
// `doe` (and everything derived from it below) stays within [0,
|
|
44
|
+
// 146096] for the rest of this function, so plain truncating `/`
|
|
45
|
+
// already equals floor division here -- only the two extractions above
|
|
46
|
+
// (on a value that CAN be negative) need `div_euclid`.
|
|
47
|
+
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365; // [0, 399]
|
|
48
|
+
let y = yoe + era * 400;
|
|
49
|
+
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
|
|
50
|
+
let mp = (5 * doy + 2) / 153; // [0, 11]
|
|
51
|
+
let d = doy - (153 * mp + 2) / 5 + 1; // [1, 31]
|
|
52
|
+
let m = if mp < 10 { mp + 3 } else { mp - 9 }; // [1, 12]
|
|
53
|
+
let y = if m <= 2 { y + 1 } else { y };
|
|
54
|
+
(y, m, d)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Parse the exact ISO-8601 shape this catalogue's contract uses --
|
|
58
|
+
/// `YYYY-MM-DDTHH:MM:SS(.mmm)?Z`, always UTC (a bare `Z`, no numeric
|
|
59
|
+
/// offset) -- into epoch milliseconds. This is the one shape every string
|
|
60
|
+
/// the golden vectors pass in uses, and the one shape [`format_iso8601`]
|
|
61
|
+
/// below ever produces, so a fixed-width positional parse (no `regex`
|
|
62
|
+
/// crate in this crate's dependency set) covers the full contract without
|
|
63
|
+
/// a general-purpose date-string grammar.
|
|
64
|
+
pub fn parse_iso8601(s: &str) -> Option<i64> {
|
|
65
|
+
let b = s.as_bytes();
|
|
66
|
+
let has_ms = match b.len() {
|
|
67
|
+
20 => false,
|
|
68
|
+
24 => true,
|
|
69
|
+
_ => return None,
|
|
70
|
+
};
|
|
71
|
+
if b[4] != b'-' || b[7] != b'-' || b[10] != b'T' || b[13] != b':' || b[16] != b':' {
|
|
72
|
+
return None;
|
|
73
|
+
}
|
|
74
|
+
if has_ms && b[19] != b'.' {
|
|
75
|
+
return None;
|
|
76
|
+
}
|
|
77
|
+
if b[b.len() - 1] != b'Z' {
|
|
78
|
+
return None;
|
|
79
|
+
}
|
|
80
|
+
let year: i64 = s.get(0..4)?.parse().ok()?;
|
|
81
|
+
let month: i64 = s.get(5..7)?.parse().ok()?;
|
|
82
|
+
let day: i64 = s.get(8..10)?.parse().ok()?;
|
|
83
|
+
let hour: i64 = s.get(11..13)?.parse().ok()?;
|
|
84
|
+
let minute: i64 = s.get(14..16)?.parse().ok()?;
|
|
85
|
+
let second: i64 = s.get(17..19)?.parse().ok()?;
|
|
86
|
+
let ms: i64 = if has_ms { s.get(20..23)?.parse().ok()? } else { 0 };
|
|
87
|
+
if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
|
|
88
|
+
return None;
|
|
89
|
+
}
|
|
90
|
+
if hour > 23 || minute > 59 || second > 60 {
|
|
91
|
+
return None;
|
|
92
|
+
}
|
|
93
|
+
let days = days_from_civil(year, month, day);
|
|
94
|
+
Some(days * MS_PER_DAY + hour * 3_600_000 + minute * 60_000 + second * 1000 + ms)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// The inverse of [`parse_iso8601`]: epoch milliseconds -> the exact JS
|
|
98
|
+
/// `Date.prototype.toISOString` shape (always millisecond precision,
|
|
99
|
+
/// always UTC).
|
|
100
|
+
pub fn format_iso8601(ms: i64) -> String {
|
|
101
|
+
let days = ms.div_euclid(MS_PER_DAY);
|
|
102
|
+
let ms_of_day = ms.rem_euclid(MS_PER_DAY); // always [0, 86_399_999]
|
|
103
|
+
let (y, m, d) = civil_from_days(days);
|
|
104
|
+
let hour = ms_of_day / 3_600_000;
|
|
105
|
+
let minute = (ms_of_day / 60_000) % 60;
|
|
106
|
+
let second = (ms_of_day / 1000) % 60;
|
|
107
|
+
let msec = ms_of_day % 1000;
|
|
108
|
+
format!("{y:04}-{m:02}-{d:02}T{hour:02}:{minute:02}:{second:02}.{msec:03}Z")
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Normalize a `date()` receiver to a single epoch-ms integer: this
|
|
112
|
+
/// runtime's own [`JsValue::Date`] unwraps directly; an ISO-8601
|
|
113
|
+
/// [`JsValue::String`] parses via [`parse_iso8601`]. Anything else (the
|
|
114
|
+
/// receiver contract's only two accepted shapes are not met) is `None`,
|
|
115
|
+
/// letting [`date`] apply its documented zero-value fallback.
|
|
116
|
+
pub fn epoch_ms_of(recv: &JsValue) -> Option<i64> {
|
|
117
|
+
match recv {
|
|
118
|
+
JsValue::Date(ms) => Some(*ms),
|
|
119
|
+
JsValue::String(s) => parse_iso8601(s),
|
|
120
|
+
_ => None,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Dispatch one of the zero-arg `Date.prototype` ops the compiler's
|
|
125
|
+
/// lowering plugin recognizes against an already-normalized epoch-ms
|
|
126
|
+
/// instant. Every accessor and `getTime` render as an INTEGER (a whole
|
|
127
|
+
/// `f64`, never fractional) -- `civil_from_days`'s inputs/outputs are all
|
|
128
|
+
/// exact integers, so no rounding is possible here regardless of how far
|
|
129
|
+
/// `ms` sits from the epoch. `getUTCMonth` is 0-based, matching JS (NOT
|
|
130
|
+
/// `civil_from_days`'s own 1-based `m`, hence the `- 1`).
|
|
131
|
+
pub fn date_op(ms: i64, op: &str) -> JsValue {
|
|
132
|
+
match op {
|
|
133
|
+
"getTime" => return JsValue::Number(ms as f64),
|
|
134
|
+
"toISOString" => return JsValue::String(format_iso8601(ms)),
|
|
135
|
+
_ => {}
|
|
136
|
+
}
|
|
137
|
+
let days = ms.div_euclid(MS_PER_DAY);
|
|
138
|
+
let ms_of_day = ms.rem_euclid(MS_PER_DAY);
|
|
139
|
+
let (y, m, d) = civil_from_days(days);
|
|
140
|
+
match op {
|
|
141
|
+
"getUTCFullYear" => JsValue::Number(y as f64),
|
|
142
|
+
"getUTCMonth" => JsValue::Number((m - 1) as f64),
|
|
143
|
+
"getUTCDate" => JsValue::Number(d as f64),
|
|
144
|
+
"getUTCHours" => JsValue::Number((ms_of_day / 3_600_000) as f64),
|
|
145
|
+
"getUTCMinutes" => JsValue::Number(((ms_of_day / 60_000) % 60) as f64),
|
|
146
|
+
"getUTCSeconds" => JsValue::Number(((ms_of_day / 1000) % 60) as f64),
|
|
147
|
+
_ => JsValue::Number(0.0),
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/// `date(recv, op)` -- the full helper: normalize `recv` (native
|
|
152
|
+
/// [`JsValue::Date`] or ISO-8601 string) then dispatch `op`. An
|
|
153
|
+
/// unrecognized receiver shape falls back to `toTime`'s documented
|
|
154
|
+
/// zero-value contract (mirrors the Go runtime's `Date`/`toTime` fallback
|
|
155
|
+
/// pair): `''` for `toISOString`, `0` for every accessor/`getTime`.
|
|
156
|
+
pub fn date(recv: &JsValue, op: &str) -> JsValue {
|
|
157
|
+
match epoch_ms_of(recv) {
|
|
158
|
+
Some(ms) => date_op(ms, op),
|
|
159
|
+
None if op == "toISOString" => JsValue::String(String::new()),
|
|
160
|
+
None => JsValue::Number(0.0),
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#[cfg(test)]
|
|
165
|
+
mod tests {
|
|
166
|
+
use super::*;
|
|
167
|
+
|
|
168
|
+
#[test]
|
|
169
|
+
fn round_trips_epoch_0() {
|
|
170
|
+
let ms = parse_iso8601("1970-01-01T00:00:00.000Z").unwrap();
|
|
171
|
+
assert_eq!(ms, 0);
|
|
172
|
+
assert_eq!(format_iso8601(ms), "1970-01-01T00:00:00.000Z");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#[test]
|
|
176
|
+
fn round_trips_pre_1970() {
|
|
177
|
+
let ms = parse_iso8601("1969-07-20T20:17:40.123Z").unwrap();
|
|
178
|
+
assert_eq!(ms, -14_182_939_877);
|
|
179
|
+
assert_eq!(format_iso8601(ms), "1969-07-20T20:17:40.123Z");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#[test]
|
|
183
|
+
fn round_trips_leap_day() {
|
|
184
|
+
let ms = parse_iso8601("2024-02-29T23:59:59.999Z").unwrap();
|
|
185
|
+
assert_eq!(format_iso8601(ms), "2024-02-29T23:59:59.999Z");
|
|
186
|
+
assert_eq!(date_op(ms, "getUTCMonth"), JsValue::Number(1.0));
|
|
187
|
+
assert_eq!(date_op(ms, "getUTCDate"), JsValue::Number(29.0));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#[test]
|
|
191
|
+
fn round_trips_far_future() {
|
|
192
|
+
let ms = parse_iso8601("9999-12-31T23:59:59.999Z").unwrap();
|
|
193
|
+
assert_eq!(format_iso8601(ms), "9999-12-31T23:59:59.999Z");
|
|
194
|
+
assert_eq!(date_op(ms, "getUTCFullYear"), JsValue::Number(9999.0));
|
|
195
|
+
}
|
|
196
|
+
}
|
package/runtime/src/evaluator.rs
CHANGED
|
@@ -306,6 +306,14 @@ pub fn to_number(v: &JsValue) -> f64 {
|
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
JsValue::Array(_) | JsValue::Object(_) => f64::NAN,
|
|
309
|
+
// Unreachable in practice: this evaluator only ever sees `JsValue`s
|
|
310
|
+
// decoded from a `serde_json::Value` ParsedExpr tree or produced by
|
|
311
|
+
// its own arithmetic, and JSON can't spell a `Date` (see the `num`
|
|
312
|
+
// module docstring) -- kept exhaustive rather than `unreachable!()`
|
|
313
|
+
// since `JsValue` is a shared, cross-module enum. `valueOf`-style
|
|
314
|
+
// (matches `runtime::js_number`'s `JsValue::Date` arm) for
|
|
315
|
+
// consistency if that ever changes.
|
|
316
|
+
JsValue::Date(ms) => *ms as f64,
|
|
309
317
|
}
|
|
310
318
|
}
|
|
311
319
|
|
|
@@ -335,6 +343,10 @@ pub fn to_string(v: &JsValue) -> String {
|
|
|
335
343
|
.collect::<Vec<_>>()
|
|
336
344
|
.join(","),
|
|
337
345
|
JsValue::Object(_) => "[object Object]".to_string(),
|
|
346
|
+
// Unreachable in practice -- see `to_number`'s `JsValue::Date` arm
|
|
347
|
+
// above. `toISOString` is the one Date->string shape this
|
|
348
|
+
// catalogue defines (matches `runtime::js_string`).
|
|
349
|
+
JsValue::Date(ms) => crate::date::format_iso8601(*ms),
|
|
338
350
|
}
|
|
339
351
|
}
|
|
340
352
|
|
|
@@ -345,6 +357,10 @@ pub fn truthy(v: &JsValue) -> bool {
|
|
|
345
357
|
JsValue::Number(n) => !n.is_nan() && *n != 0.0, // nonzero and not NaN
|
|
346
358
|
JsValue::String(s) => !s.is_empty(), // incl. the truthy "0"
|
|
347
359
|
JsValue::Array(_) | JsValue::Object(_) => true,
|
|
360
|
+
// Unreachable in practice -- see `to_number`'s `JsValue::Date` arm
|
|
361
|
+
// above. A JS Date object is always truthy, like every other
|
|
362
|
+
// object.
|
|
363
|
+
JsValue::Date(_) => true,
|
|
348
364
|
}
|
|
349
365
|
}
|
|
350
366
|
|
package/runtime/src/lib.rs
CHANGED
package/runtime/src/num.rs
CHANGED
|
@@ -94,6 +94,16 @@ pub enum JsValue {
|
|
|
94
94
|
/// contract for free, in one place, rather than re-sorting at the
|
|
95
95
|
/// JSON-encoding boundary.
|
|
96
96
|
Object(BTreeMap<String, JsValue>),
|
|
97
|
+
/// This runtime's own native `Date` receiver shape for `date()`'s
|
|
98
|
+
/// zero-arg method lowering (spec/template-helpers.md "date", #2274) --
|
|
99
|
+
/// a signed epoch-MILLISECOND integer (never a float), so a pre-1970
|
|
100
|
+
/// instant round-trips exactly with no rounding risk. The other half of
|
|
101
|
+
/// `date()`'s receiver contract, an ISO-8601 `String`, needs no
|
|
102
|
+
/// dedicated variant since [`JsValue::String`] already carries it; see
|
|
103
|
+
/// `date::epoch_ms_of`. JSON can't spell this type (`from_json` never
|
|
104
|
+
/// produces one); `to_json` renders it via its `toISOString` form,
|
|
105
|
+
/// matching JS `JSON.stringify(date)`.
|
|
106
|
+
Date(i64),
|
|
97
107
|
}
|
|
98
108
|
|
|
99
109
|
impl JsValue {
|
|
@@ -159,6 +169,9 @@ impl JsValue {
|
|
|
159
169
|
JsValue::Object(o) => {
|
|
160
170
|
JsonValue::Object(o.iter().map(|(k, v)| (k.clone(), v.to_json())).collect())
|
|
161
171
|
}
|
|
172
|
+
// Matches JS `JSON.stringify(date)`, which calls the date's own
|
|
173
|
+
// `toJSON` -> `toISOString`.
|
|
174
|
+
JsValue::Date(ms) => JsonValue::String(crate::date::format_iso8601(*ms)),
|
|
162
175
|
}
|
|
163
176
|
}
|
|
164
177
|
}
|
package/runtime/src/runtime.rs
CHANGED
|
@@ -43,9 +43,10 @@
|
|
|
43
43
|
//! operator it emits.
|
|
44
44
|
|
|
45
45
|
use crate::backend_minijinja;
|
|
46
|
+
use crate::date;
|
|
46
47
|
use crate::evaluator;
|
|
47
48
|
use crate::num::{self, JsValue};
|
|
48
|
-
use minijinja::value::{from_args, Enumerator, Kwargs, Object, Value as MjValue, ValueKind};
|
|
49
|
+
use minijinja::value::{from_args, Enumerator, Kwargs, Object, ObjectRepr, Value as MjValue, ValueKind};
|
|
49
50
|
use minijinja::{Error, ErrorKind, State};
|
|
50
51
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
|
51
52
|
use std::sync::{Arc, Mutex};
|
|
@@ -111,6 +112,11 @@ pub fn js_string(v: &JsValue) -> String {
|
|
|
111
112
|
.collect::<Vec<_>>()
|
|
112
113
|
.join(","),
|
|
113
114
|
JsValue::Object(_) => "[object Object]".to_string(),
|
|
115
|
+
// No JS `Date.prototype.toString()` port (that format -- e.g. "Fri
|
|
116
|
+
// Jul 20 1969 20:17:40 GMT+0000" -- is out of this catalogue's
|
|
117
|
+
// scope, #2274); `toISOString` is the one Date->string shape the
|
|
118
|
+
// spec defines, so a bare `bf.string(date)` uses it too.
|
|
119
|
+
JsValue::Date(ms) => date::format_iso8601(*ms),
|
|
114
120
|
}
|
|
115
121
|
}
|
|
116
122
|
|
|
@@ -125,6 +131,9 @@ pub fn js_number(v: &JsValue) -> f64 {
|
|
|
125
131
|
JsValue::Number(n) => *n,
|
|
126
132
|
JsValue::String(s) => if num::looks_like_number(s) { num::parse_number_literal(s) } else { f64::NAN },
|
|
127
133
|
JsValue::Array(_) | JsValue::Object(_) => f64::NAN,
|
|
134
|
+
// `Number(date)` mirrors JS `Date`'s default `valueOf` coercion:
|
|
135
|
+
// the epoch-ms count itself.
|
|
136
|
+
JsValue::Date(ms) => *ms as f64,
|
|
128
137
|
}
|
|
129
138
|
}
|
|
130
139
|
|
|
@@ -137,6 +146,7 @@ pub fn js_truthy(v: &JsValue) -> bool {
|
|
|
137
146
|
JsValue::Number(n) => !n.is_nan() && *n != 0.0, // not NaN, not zero
|
|
138
147
|
JsValue::String(s) => !s.is_empty(), // incl. the JS-truthy "0"
|
|
139
148
|
JsValue::Array(_) | JsValue::Object(_) => true,
|
|
149
|
+
JsValue::Date(_) => true, // a JS Date object is always truthy
|
|
140
150
|
}
|
|
141
151
|
}
|
|
142
152
|
|
|
@@ -440,13 +450,13 @@ fn is_numeric_like(v: &JsValue) -> bool {
|
|
|
440
450
|
JsValue::Null | JsValue::Bool(_) => false,
|
|
441
451
|
JsValue::Number(_) => true,
|
|
442
452
|
JsValue::String(s) => num::looks_like_number(s),
|
|
443
|
-
JsValue::Array(_) | JsValue::Object(_) => false,
|
|
453
|
+
JsValue::Array(_) | JsValue::Object(_) | JsValue::Date(_) => false,
|
|
444
454
|
}
|
|
445
455
|
}
|
|
446
456
|
|
|
447
457
|
fn numeric_value(v: &JsValue) -> f64 {
|
|
448
458
|
match v {
|
|
449
|
-
JsValue::Null | JsValue::Array(_) | JsValue::Object(_) => 0.0,
|
|
459
|
+
JsValue::Null | JsValue::Array(_) | JsValue::Object(_) | JsValue::Date(_) => 0.0,
|
|
450
460
|
JsValue::Bool(b) => if *b { 1.0 } else { 0.0 },
|
|
451
461
|
JsValue::Number(n) => *n,
|
|
452
462
|
JsValue::String(s) => if num::looks_like_number(s) { num::parse_number_literal(s) } else { 0.0 },
|
|
@@ -936,6 +946,13 @@ impl BfInstance {
|
|
|
936
946
|
format!("<!--bf-scope:{}{host_segment}{props_json}-->", self.scope_id)
|
|
937
947
|
}
|
|
938
948
|
|
|
949
|
+
/// Paired end marker for `scope_comment`, emitted after the fragment's
|
|
950
|
+
/// last top-level node. No host/props segments -- the client only needs
|
|
951
|
+
/// the scope id to close the boundary (#2289).
|
|
952
|
+
fn scope_comment_end(&self) -> String {
|
|
953
|
+
format!("<!--bf-/scope:{}-->", self.scope_id)
|
|
954
|
+
}
|
|
955
|
+
|
|
939
956
|
fn provide_context(&self, name: &str, value: JsValue) {
|
|
940
957
|
self.session.context_stacks.lock().unwrap().entry(name.to_string()).or_default().push(value);
|
|
941
958
|
}
|
|
@@ -1075,6 +1092,14 @@ impl BfInstance {
|
|
|
1075
1092
|
// ---------------------------------------------------------------------------
|
|
1076
1093
|
|
|
1077
1094
|
pub fn mj_to_js(v: &MjValue) -> JsValue {
|
|
1095
|
+
// A `JsDate`-wrapped native Date prop (see that struct's docstring)
|
|
1096
|
+
// isn't a Seq/Map/Iterable, so it would otherwise fall into the
|
|
1097
|
+
// catch-all `_ => JsValue::Null` below, silently losing the receiver
|
|
1098
|
+
// `date()` needs -- checked first, ahead of the generic `v.kind()`
|
|
1099
|
+
// dispatch.
|
|
1100
|
+
if let Some(d) = v.downcast_object_ref::<JsDate>() {
|
|
1101
|
+
return JsValue::Date(d.0);
|
|
1102
|
+
}
|
|
1078
1103
|
match v.kind() {
|
|
1079
1104
|
ValueKind::Undefined | ValueKind::None => JsValue::Null,
|
|
1080
1105
|
ValueKind::Bool => JsValue::Bool(v.is_true()),
|
|
@@ -1148,6 +1173,39 @@ pub fn js_to_mj(v: &JsValue) -> MjValue {
|
|
|
1148
1173
|
let map: BTreeMap<String, MjValue> = o.iter().map(|(k, v)| (k.clone(), js_to_mj(v))).collect();
|
|
1149
1174
|
MjValue::from(map)
|
|
1150
1175
|
}
|
|
1176
|
+
JsValue::Date(ms) => MjValue::from_object(JsDate(*ms)),
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
/// This runtime's own native "Date" value, wrapped as a minijinja
|
|
1181
|
+
/// `Object` so a host can seed a template var with a real Date-typed
|
|
1182
|
+
/// receiver instead of always going through `date()`'s ISO-8601-string
|
|
1183
|
+
/// contract half (spec/template-helpers.md "date", #2274; mirrors the Go
|
|
1184
|
+
/// runtime's `time.Time` / the Ruby runtime's `Time` as the "native"
|
|
1185
|
+
/// receiver shape). Carries nothing but the epoch-ms integer
|
|
1186
|
+
/// `JsValue::Date` already carries -- `mj_to_js`/`js_to_mj` above
|
|
1187
|
+
/// round-trip it losslessly via `downcast_object_ref`/`from_object`.
|
|
1188
|
+
#[derive(Debug)]
|
|
1189
|
+
struct JsDate(i64);
|
|
1190
|
+
|
|
1191
|
+
impl Object for JsDate {
|
|
1192
|
+
fn repr(self: &Arc<Self>) -> ObjectRepr {
|
|
1193
|
+
ObjectRepr::Plain
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// A JS `Date` object is always truthy, like every other object.
|
|
1197
|
+
fn is_true(self: &Arc<Self>) -> bool {
|
|
1198
|
+
true
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
fn render(self: &Arc<Self>, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
|
|
1202
|
+
where
|
|
1203
|
+
Self: Sized + 'static,
|
|
1204
|
+
{
|
|
1205
|
+
// `toISOString` is the one Date->string shape this catalogue
|
|
1206
|
+
// defines (see `js_string`'s `JsValue::Date` arm); a bare
|
|
1207
|
+
// interpolation of a native Date value uses the same shape.
|
|
1208
|
+
write!(f, "{}", date::format_iso8601(self.0))
|
|
1151
1209
|
}
|
|
1152
1210
|
}
|
|
1153
1211
|
|
|
@@ -1240,6 +1298,7 @@ impl Object for BfInstance {
|
|
|
1240
1298
|
"text_start" => Ok(safe(format!("<!--bf:{}-->", js_string(a(0))))),
|
|
1241
1299
|
"text_end" => Ok(safe("<!--/-->".to_string())),
|
|
1242
1300
|
"scope_comment" => Ok(safe(self.scope_comment())),
|
|
1301
|
+
"scope_comment_end" => Ok(safe(self.scope_comment_end())),
|
|
1243
1302
|
|
|
1244
1303
|
// -- Script registration ---------------------------------------
|
|
1245
1304
|
"register_script" => {
|
|
@@ -1272,6 +1331,13 @@ impl Object for BfInstance {
|
|
|
1272
1331
|
"max" => Ok(MjValue::from(num::js_max(js_number(a(0)), js_number(a(1))))),
|
|
1273
1332
|
"abs" => Ok(MjValue::from(num::js_abs(js_number(a(0))))),
|
|
1274
1333
|
"to_fixed" => Ok(MjValue::from(num::to_fixed(js_number(a(0)), num::to_f64(a(1)) as i32))),
|
|
1334
|
+
// `date(recv, op)` -- zero-arg `Date.prototype` method
|
|
1335
|
+
// lowering (spec/template-helpers.md "date", #2274). `recv` is
|
|
1336
|
+
// either a `JsDate`-wrapped native receiver (already converted
|
|
1337
|
+
// to `JsValue::Date` by `mj_to_js` above) or an ISO-8601
|
|
1338
|
+
// string; `date::date` normalizes and dispatches both. `op`
|
|
1339
|
+
// stays a plain string arg, never routed through `js_number`.
|
|
1340
|
+
"date" => Ok(js_to_mj(&date::date(a(0), a(1).as_str().unwrap_or("")))),
|
|
1275
1341
|
|
|
1276
1342
|
// -- Array / string method helpers (#1448 Tier A) ------------------
|
|
1277
1343
|
"includes" => Ok(MjValue::from(includes(a(0), a(1)))),
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
//! `tests/vector-divergences.json`.
|
|
25
25
|
|
|
26
26
|
use barefootjs::backend_minijinja;
|
|
27
|
+
use barefootjs::date;
|
|
27
28
|
use barefootjs::num::{self, JsValue};
|
|
28
29
|
use barefootjs::runtime;
|
|
29
30
|
use barefootjs::SearchParams;
|
|
@@ -44,6 +45,31 @@ fn obj(v: &JsValue, key: &str) -> JsValue {
|
|
|
44
45
|
v.as_object().and_then(|m| m.get(key)).cloned().unwrap_or(JsValue::Null)
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
/// Materializes the `{"$date": "<ISO>"}` native-date arg sentinel (#2288)
|
|
49
|
+
/// into this runtime's own `JsValue::Date`, so `date()`'s native-receiver
|
|
50
|
+
/// branch (not just its ISO-string branch) is exercised. Recurses through
|
|
51
|
+
/// arrays/objects the same shape the Perl port's `normalize_arg` walks,
|
|
52
|
+
/// since a vector's `args` may nest the sentinel inside a higher-order
|
|
53
|
+
/// projection payload. Panics on an unparseable `$date` string -- the
|
|
54
|
+
/// corpus is generated from a fixed set of known-good ISO instants, so a
|
|
55
|
+
/// parse failure here means the harness itself is broken, not the case
|
|
56
|
+
/// under test.
|
|
57
|
+
fn materialize_arg(v: &JsonValue) -> JsValue {
|
|
58
|
+
if let Some(o) = v.as_object() {
|
|
59
|
+
if let Some(iso) = o.get("$date").and_then(|d| d.as_str()) {
|
|
60
|
+
if o.len() == 1 {
|
|
61
|
+
let ms = date::parse_iso8601(iso).unwrap_or_else(|| panic!("materialize_arg: invalid $date {iso:?}"));
|
|
62
|
+
return JsValue::Date(ms);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return JsValue::Object(o.iter().map(|(k, x)| (k.clone(), materialize_arg(x))).collect());
|
|
66
|
+
}
|
|
67
|
+
if let Some(a) = v.as_array() {
|
|
68
|
+
return JsValue::Array(a.iter().map(materialize_arg).collect());
|
|
69
|
+
}
|
|
70
|
+
JsValue::from_json(v)
|
|
71
|
+
}
|
|
72
|
+
|
|
47
73
|
/// Mirrors `test_helper_vectors.py`'s `_truthy_pred`.
|
|
48
74
|
fn truthy_pred(item: &JsValue, field: &str) -> bool {
|
|
49
75
|
runtime::js_truthy(&obj(item, field))
|
|
@@ -123,6 +149,7 @@ fn call_binding(fn_name: &str, args: &[JsValue]) -> Option<JsValue> {
|
|
|
123
149
|
let digits = if args.len() > 1 { num::to_f64(&a(1)) as i32 } else { 0 };
|
|
124
150
|
JsValue::String(num::to_fixed(runtime::js_number(&a(0)), digits))
|
|
125
151
|
}
|
|
152
|
+
"date" => date::date(&a(0), a(1).as_str().unwrap_or("")),
|
|
126
153
|
"lower" => JsValue::String(runtime::js_string(&a(0)).to_lowercase()),
|
|
127
154
|
"upper" => JsValue::String(runtime::js_string(&a(0)).to_uppercase()),
|
|
128
155
|
"trim" => JsValue::String(runtime::trim(&a(0))),
|
|
@@ -237,6 +264,11 @@ fn py_truthy(v: &JsValue) -> bool {
|
|
|
237
264
|
JsValue::String(s) => !s.is_empty(),
|
|
238
265
|
JsValue::Array(a) => !a.is_empty(),
|
|
239
266
|
JsValue::Object(o) => !o.is_empty(),
|
|
267
|
+
// Unreachable: no binding in this file ever returns a
|
|
268
|
+
// `JsValue::Date` (`date()` itself always returns `Number` or
|
|
269
|
+
// `String`, per its own return type) -- kept exhaustive since
|
|
270
|
+
// `JsValue` is a shared enum.
|
|
271
|
+
JsValue::Date(_) => true,
|
|
240
272
|
}
|
|
241
273
|
}
|
|
242
274
|
|
|
@@ -298,7 +330,7 @@ fn helper_vectors_match_js_reference_or_declared_divergence() {
|
|
|
298
330
|
let note = case["note"].as_str().unwrap();
|
|
299
331
|
let key = format!("{fn_name}/{note}");
|
|
300
332
|
let raw_args = case["args"].as_array().cloned().unwrap_or_default();
|
|
301
|
-
let args: Vec<JsValue> = raw_args.iter().map(
|
|
333
|
+
let args: Vec<JsValue> = raw_args.iter().map(materialize_arg).collect();
|
|
302
334
|
let expect = &case["expect"];
|
|
303
335
|
|
|
304
336
|
if let Some(reason) = div_file.unsupported.get(fn_name) {
|
|
@@ -530,3 +530,28 @@ function Widget({ rows }: { rows: { x: string }[] }) {
|
|
|
530
530
|
expect(template).toContain('bf.string(cfg.x)')
|
|
531
531
|
})
|
|
532
532
|
})
|
|
533
|
+
|
|
534
|
+
describe('MinijinjaAdapter - fragment-root scope comment end marker (#2289)', () => {
|
|
535
|
+
// A fragment-rooted component (multiple top-level nodes) uses a
|
|
536
|
+
// comment-based scope marker instead of a real DOM element, so it needs a
|
|
537
|
+
// paired end marker after the last top-level node -- otherwise
|
|
538
|
+
// client-side queries from this scope leak onto later siblings owned by
|
|
539
|
+
// the parent. See `scope_comment_end` in `runtime/src/runtime.rs`.
|
|
540
|
+
test('emits a bf-/scope end marker with the same scope id as the begin marker', () => {
|
|
541
|
+
const { template } = compileAndGenerate(`
|
|
542
|
+
export function Wrapper({ children }: { children?: any }) {
|
|
543
|
+
return (
|
|
544
|
+
<>
|
|
545
|
+
<div>Header</div>
|
|
546
|
+
{children}
|
|
547
|
+
</>
|
|
548
|
+
)
|
|
549
|
+
}
|
|
550
|
+
`)
|
|
551
|
+
expect(template).toContain('{{ bf.scope_comment() | safe }}')
|
|
552
|
+
expect(template).toContain('{{ bf.scope_comment_end() | safe }}')
|
|
553
|
+
expect(template.indexOf('bf.scope_comment()')).toBeLessThan(
|
|
554
|
+
template.indexOf('bf.scope_comment_end()'),
|
|
555
|
+
)
|
|
556
|
+
})
|
|
557
|
+
})
|
|
@@ -1262,7 +1262,10 @@ export class MinijinjaAdapter extends BaseAdapter implements IRNodeEmitter<Jinja
|
|
|
1262
1262
|
private renderFragment(fragment: IRFragment): string {
|
|
1263
1263
|
const children = this.renderChildren(fragment.children)
|
|
1264
1264
|
if (fragment.needsScopeComment) {
|
|
1265
|
-
|
|
1265
|
+
// End marker bounds the scope's sibling range -- without it, queries
|
|
1266
|
+
// from the fragment scope leak onto later siblings owned by the
|
|
1267
|
+
// parent (#2289).
|
|
1268
|
+
return `{{ bf.scope_comment() | safe }}${children}{{ bf.scope_comment_end() | safe }}`
|
|
1266
1269
|
}
|
|
1267
1270
|
return children
|
|
1268
1271
|
}
|
package/src/test-render.ts
CHANGED
|
@@ -529,6 +529,15 @@ function buildVars(
|
|
|
529
529
|
* uniformly rather than threading the transform through each builder.
|
|
530
530
|
*/
|
|
531
531
|
function encodeSpecials(value: unknown): unknown {
|
|
532
|
+
if (value instanceof Date) {
|
|
533
|
+
// A Date prop crosses the language boundary as its ISO-8601 string —
|
|
534
|
+
// the shared `date()` runtime helper (#2274/#2288) accepts an ISO
|
|
535
|
+
// string as well as a native `chrono` date type. MUST come before the
|
|
536
|
+
// generic object branch below, which REBUILDS the object via
|
|
537
|
+
// `Object.entries()` — a Date's fields are non-enumerable, so that
|
|
538
|
+
// rebuild would silently erase it into `{}`.
|
|
539
|
+
return value.toISOString()
|
|
540
|
+
}
|
|
532
541
|
if (typeof value === 'number') {
|
|
533
542
|
if (Number.isNaN(value)) return { __bf_special: 'nan' }
|
|
534
543
|
if (value === Infinity) return { __bf_special: 'inf' }
|