@barefootjs/rust 0.21.4 → 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 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/2087" }
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/2087" }
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/2038" }
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/2038" }
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/2215" }],
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.21.4",
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.21.4"
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.21.4",
64
+ "@barefootjs/jsx": "0.23.0",
65
65
  "typescript": "^5.0.0"
66
66
  }
67
67
  }
@@ -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::*;
@@ -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))),
@@ -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/2087.
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/2087' },
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/2087' },
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/2038' },
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/2038' },
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/2215' }],
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