@barefootjs/rust 0.21.3 → 0.23.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/index.js +5 -5
- package/package.json +3 -3
- package/runtime/src/date.rs +95 -0
- package/runtime/src/runtime.rs +6 -0
- package/runtime/tests/helper_vectors.rs +1 -0
- package/src/conformance-pins.ts +8 -8
package/dist/index.js
CHANGED
|
@@ -189190,18 +189190,18 @@ var minijinjaAdapter = new MinijinjaAdapter;
|
|
|
189190
189190
|
// src/conformance-pins.ts
|
|
189191
189191
|
var conformancePins = {
|
|
189192
189192
|
"static-array-from-props": [
|
|
189193
|
-
{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/
|
|
189193
|
+
{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2321" }
|
|
189194
189194
|
],
|
|
189195
189195
|
"static-array-from-props-with-component": [
|
|
189196
|
-
{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/
|
|
189196
|
+
{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2321" }
|
|
189197
189197
|
],
|
|
189198
189198
|
"filter-nested-callback-predicate": [
|
|
189199
|
-
{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/
|
|
189199
|
+
{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2320" }
|
|
189200
189200
|
],
|
|
189201
189201
|
"filter-nested-find-predicate": [
|
|
189202
|
-
{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/
|
|
189202
|
+
{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2320" }
|
|
189203
189203
|
],
|
|
189204
|
-
"dangerous-inner-html-dynamic": [{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/
|
|
189204
|
+
"dangerous-inner-html-dynamic": [{ code: "BF101", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2319" }],
|
|
189205
189205
|
"date-method-uncatalogued": [{ code: "BF021", severity: "error", issue: "https://github.com/piconic-ai/barefootjs/issues/2274" }]
|
|
189206
189206
|
};
|
|
189207
189207
|
// src/render-divergences.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/rust",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.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.23.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.23.0",
|
|
65
65
|
"typescript": "^5.0.0"
|
|
66
66
|
}
|
|
67
67
|
}
|
package/runtime/src/date.rs
CHANGED
|
@@ -161,6 +161,101 @@ pub fn date(recv: &JsValue, op: &str) -> JsValue {
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
/// Parse a fixed UTC offset `tz` matching `^([+-])(\d{2}):(\d{2})$` into
|
|
165
|
+
/// signed minutes -- mirrors `OFFSET_RE` in
|
|
166
|
+
/// `packages/client/src/format-date.ts` and `tzOffsetRE` in the Go
|
|
167
|
+
/// runtime's `bf.go`. ANY other shape (`"UTC"`, an IANA zone name, a
|
|
168
|
+
/// malformed offset like `"+9:00"`) is not this exact 6-byte
|
|
169
|
+
/// sign/digit/digit/colon/digit/digit layout and normalizes to `0`
|
|
170
|
+
/// minutes (UTC) -- byte/ascii-digit checks here, not a `regex` crate
|
|
171
|
+
/// dependency (this crate's fixed dependency list excludes `regex`, same
|
|
172
|
+
/// as this module's docstring notes for `chrono`).
|
|
173
|
+
fn parse_tz_offset(tz: &str) -> i64 {
|
|
174
|
+
let b = tz.as_bytes();
|
|
175
|
+
if b.len() != 6 || b[3] != b':' {
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
let sign = match b[0] {
|
|
179
|
+
b'+' => 1i64,
|
|
180
|
+
b'-' => -1i64,
|
|
181
|
+
_ => return 0,
|
|
182
|
+
};
|
|
183
|
+
if !b[1].is_ascii_digit() || !b[2].is_ascii_digit() || !b[4].is_ascii_digit() || !b[5].is_ascii_digit() {
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
let hh = (b[1] - b'0') as i64 * 10 + (b[2] - b'0') as i64;
|
|
187
|
+
let mm = (b[4] - b'0') as i64 * 10 + (b[5] - b'0') as i64;
|
|
188
|
+
sign * (hh * 60 + mm)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// `format_date(recv, pattern, tz)` -- the lowering target for a
|
|
192
|
+
/// `formatDate(date, pattern, timeZone)` call (spec/template-helpers.md
|
|
193
|
+
/// "format_date", #2324): a total, deterministic date-pattern formatter --
|
|
194
|
+
/// no locale, no host timezone, no `SystemTime::now`. Mirrors
|
|
195
|
+
/// `packages/client/src/format-date.ts` (the JS-normative reference) and
|
|
196
|
+
/// the Go runtime's `FormatDate` byte-for-byte.
|
|
197
|
+
///
|
|
198
|
+
/// `recv` follows the `date` helper's receiver contract exactly (see
|
|
199
|
+
/// [`epoch_ms_of`]): this runtime's own [`JsValue::Date`] or an ISO-8601
|
|
200
|
+
/// string, both normalized to a single epoch-ms instant. A `None`
|
|
201
|
+
/// (nil/unset or unparseable) receiver renders `""` -- unlike [`date`],
|
|
202
|
+
/// there is no accessor/`getTime` numeric fallback here, since
|
|
203
|
+
/// `format_date` always returns a string.
|
|
204
|
+
///
|
|
205
|
+
/// `tz` resolves via [`parse_tz_offset`]; the instant is shifted by
|
|
206
|
+
/// `offset_minutes * 60_000` ms and the shifted instant's UTC calendar
|
|
207
|
+
/// fields (not the original instant's) are what the pattern tokens read --
|
|
208
|
+
/// the shifted UTC clock face IS the local clock face at that offset.
|
|
209
|
+
///
|
|
210
|
+
/// `pattern` is scanned left-to-right, longest-match, for the token set
|
|
211
|
+
/// `YYYY|MM|DD|M|D` (checking the 4-char token before the 2-char tokens
|
|
212
|
+
/// before the 1-char tokens at each position, the same alternation-order
|
|
213
|
+
/// discipline the Go/JS ports use); every other character -- including
|
|
214
|
+
/// multi-byte ones like 年/月/日 -- passes through literally. The scan
|
|
215
|
+
/// advances by whole characters only (ASCII token matches consume exactly
|
|
216
|
+
/// 1/2/4 ASCII bytes; the fallback branch consumes one full `char` via
|
|
217
|
+
/// `len_utf8`), so slicing never lands mid-codepoint. `YYYY` is
|
|
218
|
+
/// `abs(year)` zero-padded to 4 digits, `-`-prefixed for a negative year;
|
|
219
|
+
/// `MM`/`DD` zero-pad to 2; `M`/`D` are bare.
|
|
220
|
+
pub fn format_date(recv: &JsValue, pattern: &str, tz: &str) -> String {
|
|
221
|
+
let ms = match epoch_ms_of(recv) {
|
|
222
|
+
Some(ms) => ms,
|
|
223
|
+
None => return String::new(),
|
|
224
|
+
};
|
|
225
|
+
let offset_minutes = parse_tz_offset(tz);
|
|
226
|
+
let shifted = ms + offset_minutes * 60_000;
|
|
227
|
+
let days = shifted.div_euclid(MS_PER_DAY);
|
|
228
|
+
let (year, month, day) = civil_from_days(days);
|
|
229
|
+
let yyyy = if year < 0 { format!("-{:04}", -year) } else { format!("{year:04}") };
|
|
230
|
+
|
|
231
|
+
let mut out = String::with_capacity(pattern.len());
|
|
232
|
+
let mut i = 0;
|
|
233
|
+
while i < pattern.len() {
|
|
234
|
+
let rest = &pattern[i..];
|
|
235
|
+
if rest.starts_with("YYYY") {
|
|
236
|
+
out.push_str(&yyyy);
|
|
237
|
+
i += 4;
|
|
238
|
+
} else if rest.starts_with("MM") {
|
|
239
|
+
out.push_str(&format!("{month:02}"));
|
|
240
|
+
i += 2;
|
|
241
|
+
} else if rest.starts_with("DD") {
|
|
242
|
+
out.push_str(&format!("{day:02}"));
|
|
243
|
+
i += 2;
|
|
244
|
+
} else if rest.starts_with('M') {
|
|
245
|
+
out.push_str(&month.to_string());
|
|
246
|
+
i += 1;
|
|
247
|
+
} else if rest.starts_with('D') {
|
|
248
|
+
out.push_str(&day.to_string());
|
|
249
|
+
i += 1;
|
|
250
|
+
} else {
|
|
251
|
+
let ch = rest.chars().next().expect("i < pattern.len() guarantees a char remains");
|
|
252
|
+
out.push(ch);
|
|
253
|
+
i += ch.len_utf8();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
out
|
|
257
|
+
}
|
|
258
|
+
|
|
164
259
|
#[cfg(test)]
|
|
165
260
|
mod tests {
|
|
166
261
|
use super::*;
|
package/runtime/src/runtime.rs
CHANGED
|
@@ -1338,6 +1338,12 @@ impl Object for BfInstance {
|
|
|
1338
1338
|
// string; `date::date` normalizes and dispatches both. `op`
|
|
1339
1339
|
// stays a plain string arg, never routed through `js_number`.
|
|
1340
1340
|
"date" => Ok(js_to_mj(&date::date(a(0), a(1).as_str().unwrap_or("")))),
|
|
1341
|
+
// `format_date(recv, pattern, tz)` (spec/template-helpers.md
|
|
1342
|
+
// "format_date", #2324) -- total, locale-free date-pattern
|
|
1343
|
+
// formatting layered on the same `recv` normalization as
|
|
1344
|
+
// `date` above. See `date::format_date`'s docstring for the
|
|
1345
|
+
// full contract.
|
|
1346
|
+
"format_date" => Ok(MjValue::from(date::format_date(a(0), a(1).as_str().unwrap_or(""), a(2).as_str().unwrap_or("")))),
|
|
1341
1347
|
|
|
1342
1348
|
// -- Array / string method helpers (#1448 Tier A) ------------------
|
|
1343
1349
|
"includes" => Ok(MjValue::from(includes(a(0), a(1)))),
|
|
@@ -150,6 +150,7 @@ fn call_binding(fn_name: &str, args: &[JsValue]) -> Option<JsValue> {
|
|
|
150
150
|
JsValue::String(num::to_fixed(runtime::js_number(&a(0)), digits))
|
|
151
151
|
}
|
|
152
152
|
"date" => date::date(&a(0), a(1).as_str().unwrap_or("")),
|
|
153
|
+
"format_date" => JsValue::String(date::format_date(&a(0), a(1).as_str().unwrap_or(""), a(2).as_str().unwrap_or(""))),
|
|
153
154
|
"lower" => JsValue::String(runtime::js_string(&a(0)).to_lowercase()),
|
|
154
155
|
"upper" => JsValue::String(runtime::js_string(&a(0)).to_uppercase()),
|
|
155
156
|
"trim" => JsValue::String(runtime::trim(&a(0))),
|
package/src/conformance-pins.ts
CHANGED
|
@@ -42,15 +42,15 @@ export const conformancePins: ConformancePins = {
|
|
|
42
42
|
// diagnostic (mirrors adapter-jinja's identical check). Fixing the
|
|
43
43
|
// underlying gap (computed-array-from-props as a loop source) is out of
|
|
44
44
|
// scope for #2087; tracked as a follow-up at
|
|
45
|
-
// https://github.com/piconic-ai/barefootjs/issues/
|
|
45
|
+
// https://github.com/piconic-ai/barefootjs/issues/2321.
|
|
46
46
|
'static-array-from-props': [
|
|
47
|
-
{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/
|
|
47
|
+
{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2321' },
|
|
48
48
|
],
|
|
49
49
|
// BF101 (unresolvable computed loop array, see above) fires; BF103
|
|
50
50
|
// (imported child in the loop body) no longer does now that the
|
|
51
51
|
// conformance harness passes `siblingTemplatesRegistered: true` (#2205).
|
|
52
52
|
'static-array-from-props-with-component': [
|
|
53
|
-
{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/
|
|
53
|
+
{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2321' },
|
|
54
54
|
],
|
|
55
55
|
// Rest-destructure `.map()` callbacks (#2087 Phase B): every shape now
|
|
56
56
|
// lowers natively — fixed bindings at any depth/shape via a chained
|
|
@@ -80,12 +80,12 @@ export const conformancePins: ConformancePins = {
|
|
|
80
80
|
// lossy, same as xslate. The `/* @client */` twin
|
|
81
81
|
// (`filter-nested-callback-predicate-client`) has no pin here: it must
|
|
82
82
|
// render clean on every adapter, which asserts the suppression contract.
|
|
83
|
-
// https://github.com/piconic-ai/barefootjs/issues/2038
|
|
83
|
+
// Faithful lowering tracked: https://github.com/piconic-ai/barefootjs/issues/2320 (successor to #2038)
|
|
84
84
|
'filter-nested-callback-predicate': [
|
|
85
|
-
{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/
|
|
85
|
+
{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2320' },
|
|
86
86
|
],
|
|
87
87
|
'filter-nested-find-predicate': [
|
|
88
|
-
{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/
|
|
88
|
+
{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2320' },
|
|
89
89
|
],
|
|
90
90
|
// NB: TOP-LEVEL `.find` / `.findIndex` / `.findLast` / `.findLastIndex`
|
|
91
91
|
// (text position) are NOT pinned here — like xslate (unlike mojo, which
|
|
@@ -101,8 +101,8 @@ export const conformancePins: ConformancePins = {
|
|
|
101
101
|
// `dangerouslySetInnerHTML={{ __html: '...' }}` is spliced directly into
|
|
102
102
|
// the template as trusted raw text (`resolveDangerousInnerHtml`, #2207).
|
|
103
103
|
// A dynamic/signal-derived value still refuses with BF101 — see the
|
|
104
|
-
// `dangerous-inner-html-dynamic` fixture/pin below (tracked: #2215).
|
|
105
|
-
'dangerous-inner-html-dynamic': [{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/
|
|
104
|
+
// `dangerous-inner-html-dynamic` fixture/pin below (tracked: #2319, successor to #2215).
|
|
105
|
+
'dangerous-inner-html-dynamic': [{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2319' }],
|
|
106
106
|
// #2273: a method call on a prop typed as a built-in host rich type
|
|
107
107
|
// (Date, Map, …) has no catalogued lowering in any adapter — this is a
|
|
108
108
|
// compiler-level refusal (`checkRichTypeMethodCalls`, wired ahead of
|