diff --git a/docs/runtime/toml.mdx b/docs/runtime/toml.mdx index 8414d9ae1774..c0a85b2ea23d 100644 --- a/docs/runtime/toml.mdx +++ b/docs/runtime/toml.mdx @@ -51,7 +51,7 @@ Bun's TOML parser implements the full [TOML v1.1.0 specification](https://github - **Integers**: decimal, hex (`0x`), octal (`0o`), and binary (`0b`). Integers that cannot be represented losslessly as a JavaScript number — outside ±(2^53 - 1) — throw - **Floats**: including `inf` and `nan` - **Booleans**: `true` and `false` -- **Date/times**: offset date-time, local date-time, local date, and local time, returned as strings of their source text +- **Date/times**: returned as [Temporal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal) objects — offset date-time as `Temporal.Instant`, local date-time as `Temporal.PlainDateTime`, local date as `Temporal.PlainDate`, and local time as `Temporal.PlainTime` - **Arrays**: including mixed types and nested arrays - **Tables**: standard (`[table]`) and inline (`{ key = "value" }`), including TOML 1.1 multi-line inline tables - **Array of tables**: `[[array]]` @@ -81,6 +81,25 @@ role = "backend" `); ``` +#### Date/times + +Each of TOML's four date/time types maps 1:1 onto a Temporal type. Temporal carries nanosecond precision; as the TOML spec permits, fractional seconds beyond nine digits are truncated: + +```ts +const doc = Bun.TOML.parse(` +created = 1979-05-27T00:32:00-07:00 # offset date-time +meeting = 1979-05-27T07:32:00 # local date-time +birthday = 1979-05-27 # local date +opens = 07:32:00 # local time +`); + +doc.created; // Temporal.Instant (an offset date-time specifies an instant; +// the written offset normalizes away: 1979-05-27T07:32:00Z) +doc.meeting; // Temporal.PlainDateTime +doc.birthday; // Temporal.PlainDate +doc.opens; // Temporal.PlainTime +``` + #### Error Handling `Bun.TOML.parse()` throws a `SyntaxError` if the TOML is invalid: @@ -118,11 +137,20 @@ Bun.TOML.stringify({ // x = 2 ``` -The top-level value must be an object — a TOML document is a table. `Date` -values become TOML offset date-times. Because TOML cannot represent them, -`null` values, `BigInt`, and circular structures throw; `undefined`, +The top-level value must be an object — a TOML document is a table. +`Temporal.Instant`, `Temporal.PlainDateTime`, `Temporal.PlainDate`, and +`Temporal.PlainTime` values become the corresponding TOML date/time +literals, so `stringify(parse(doc))` round-trips date/time types. +`Temporal.ZonedDateTime` becomes an offset date-time and `Date` becomes +an offset date-time in UTC. TOML has no syntax for time-zone or calendar +annotations, so those are dropped (the ISO fields are written), and its +years are four digits, so date values outside 0000–9999 and invalid +`Date`s throw. Because TOML cannot represent them, `null` values, +`BigInt`, circular structures, `Temporal.PlainYearMonth`, +`Temporal.PlainMonthDay`, and `Temporal.Duration` also throw; `undefined`, function, and symbol properties are skipped (inside arrays they throw, -since TOML arrays cannot have holes). +since TOML arrays cannot have holes), and passing one of those as the +top-level value returns `undefined`, as `JSON.stringify` does. --- diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 175f4b6f4b4e..0d011bf6c3b5 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -792,9 +792,12 @@ declare module "bun" { /** * Parse a TOML (v1.1.0) document into a JavaScript object. * - * Date/time values parse as strings of their source text. Integers - * outside `Number.MAX_SAFE_INTEGER` throw, since they cannot be - * represented losslessly as JavaScript numbers. + * Date/time values parse as Temporal objects: offset date-times as + * `Temporal.Instant`, local date-times as `Temporal.PlainDateTime`, + * local dates as `Temporal.PlainDate`, and local times as + * `Temporal.PlainTime`. Integers outside `Number.MAX_SAFE_INTEGER` + * throw, since they cannot be represented losslessly as JavaScript + * numbers. * * @category Utilities * @@ -810,8 +813,15 @@ declare module "bun" { * Serialize a JavaScript object to a TOML document. * * The top-level value must be an object (a TOML document is a table). - * `Date` values become TOML offset date-times. `null`, `BigInt`, and - * circular structures throw, since TOML cannot represent them; + * `Temporal.Instant`, `Temporal.PlainDateTime`, `Temporal.PlainDate`, + * and `Temporal.PlainTime` values become the corresponding TOML + * date/time literals, `Temporal.ZonedDateTime` becomes an offset + * date-time, and `Date` becomes an offset date-time in UTC; time-zone + * and calendar annotations are dropped, since TOML has no syntax for + * them. `null`, `BigInt`, circular structures, invalid `Date`s, date + * values outside years 0000–9999, and Temporal types with no TOML form + * (`Temporal.PlainYearMonth`, `Temporal.PlainMonthDay`, + * `Temporal.Duration`) throw, since TOML cannot represent them; * `undefined`, function, and symbol properties are skipped (inside * arrays they throw, since TOML arrays cannot have holes). * diff --git a/src/ast/e.rs b/src/ast/e.rs index d5b977566cd8..bcdd9490294d 100644 --- a/src/ast/e.rs +++ b/src/ast/e.rs @@ -1643,6 +1643,33 @@ pub struct Spread { pub value: ExprNodeIndex, } +/// Discriminants are shared with the C++ switch in +/// `Bun__Temporal__fromDateTimeLiteral`. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[repr(u8)] +pub enum TomlDateTimeKind { + /// `1979-05-27T00:32:00-07:00` → `Temporal.Instant` + OffsetDateTime = 1, + /// `1979-05-27T07:32:00` → `Temporal.PlainDateTime` + LocalDateTime = 2, + /// `1979-05-27` → `Temporal.PlainDate` + LocalDate = 3, + /// `07:32:00` → `Temporal.PlainTime` + LocalTime = 4, +} + +impl TomlDateTimeKind { + /// Unqualified Temporal class name (`Instant`, `PlainDateTime`, …). + pub fn temporal_class(self) -> &'static [u8] { + match self { + TomlDateTimeKind::OffsetDateTime => b"Instant", + TomlDateTimeKind::LocalDateTime => b"PlainDateTime", + TomlDateTimeKind::LocalDate => b"PlainDate", + TomlDateTimeKind::LocalTime => b"PlainTime", + } + } +} + /// JavaScript string literal type // repr(C, align(8)): `StoreStr`/`StoreRef` are `packed(4)`, so under // `repr(Rust)` the `data.ptr: NonNull` lands at a 4-but-not-8-aligned @@ -1668,6 +1695,11 @@ pub struct EString { pub rope_len: u32, pub prefer_template: bool, pub is_utf16: bool, + /// Set only by the TOML parser on a date/time literal (`data` is its + /// ASCII source text). The TOML AST never enters the JS visit passes; + /// the sinks that materialize or print it check this tag and produce a + /// Temporal value instead of a string. + pub toml_datetime: Option, } // Also exported as `String`; `EString` avoids colliding with bun_core::String. pub use EString as String; @@ -1681,6 +1713,7 @@ impl Default for EString { end: None, rope_len: 0, is_utf16: false, + toml_datetime: None, } } } @@ -1728,6 +1761,7 @@ impl EString { end: None, rope_len: 0, is_utf16: false, + toml_datetime: None, } } /// `data` is arena-owned (source text or `Expr.Data.Store` / bump arena) @@ -1738,6 +1772,16 @@ impl EString { ..Default::default() } } + + /// A TOML date/time literal; `data` must be ASCII text `Temporal.*.from` + /// accepts verbatim. + pub fn init_toml_datetime(data: &[u8], kind: TomlDateTimeKind) -> Self { + Self { + data: Str::new(data), + toml_datetime: Some(kind), + ..Default::default() + } + } /// Construct from a UTF-16 slice (arena-owned). The `data` slice's `.len()` /// stores the **u16 element count** (not byte count); `slice16()` and /// friends rely on this. The pointer is reinterpreted to `*const u8` for @@ -1964,6 +2008,7 @@ impl EString { end: self.end, rope_len: self.rope_len, is_utf16: self.is_utf16, + toml_datetime: self.toml_datetime, } } diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 7fff5ed55968..e61f8fa11a02 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -2153,6 +2153,7 @@ impl Data { end: el.end, rope_len: el.rope_len, is_utf16: el.is_utf16, + toml_datetime: el.toml_datetime, }); Ok(Data::EString(StoreRef::from_bump(item))) } diff --git a/src/bundler/ParseTask.rs b/src/bundler/ParseTask.rs index d9020c420c3a..bf13ab135e1a 100644 --- a/src/bundler/ParseTask.rs +++ b/src/bundler/ParseTask.rs @@ -607,7 +607,8 @@ pub mod parse_worker { // is disjoint from any other field the caller may hold a pointer to. let define = unsafe { &mut (*transpiler).options.define }; let mut ast = JSAst::init( - js_parser::new_lazy_export_ast(bump, define, opts, log, root, source, b"")?.unwrap(), + js_parser::new_lazy_export_ast(bump, define, opts, log, root, source, b"")? + .ok_or(AnyError::ParserError)?, ); ast.css = Some(crate::bundled_ast::CssAstRef::from_bump( bump.alloc(bun_css::BundlerStyleSheet::empty()), @@ -626,7 +627,8 @@ pub mod parse_worker { // SAFETY: see `get_empty_css_ast` — disjoint field of a live `*mut Transpiler`. let define = unsafe { &mut (*transpiler).options.define }; Ok(JSAst::init( - js_parser::new_lazy_export_ast(bump, define, opts, log, root, source, b"")?.unwrap(), + js_parser::new_lazy_export_ast(bump, define, opts, log, root, source, b"")? + .ok_or(AnyError::ParserError)?, )) } @@ -763,7 +765,7 @@ pub mod parse_worker { source, b"", )? - .unwrap(), + .ok_or(AnyError::ParserError)?, )); } Loader::Toml => { @@ -786,7 +788,7 @@ pub mod parse_worker { source, b"", )? - .unwrap(), + .ok_or(AnyError::ParserError)?, )) })(); let _ = temp_log.clone_to_with_recycled(log, true); @@ -812,7 +814,7 @@ pub mod parse_worker { source, b"", )? - .unwrap(), + .ok_or(AnyError::ParserError)?, )) })(); let _ = temp_log.clone_to_with_recycled(log, true); @@ -834,7 +836,7 @@ pub mod parse_worker { source, b"", )? - .unwrap(), + .ok_or(AnyError::ParserError)?, )) })(); let _ = temp_log.clone_to_with_recycled(log, true); @@ -865,7 +867,7 @@ pub mod parse_worker { source, b"", )? - .unwrap(), + .ok_or(AnyError::ParserError)?, )) })(); let _ = temp_log.clone_to_with_recycled(log, true); @@ -889,7 +891,7 @@ pub mod parse_worker { source, b"", )? - .unwrap(), + .ok_or(AnyError::ParserError)?, ); ast.add_url_for_css( bump, @@ -930,7 +932,7 @@ pub mod parse_worker { source, b"", )? - .unwrap(), + .ok_or(AnyError::ParserError)?, ); ast.add_url_for_css( bump, @@ -1059,7 +1061,7 @@ pub mod parse_worker { source, b"", )? - .unwrap(), + .ok_or(AnyError::ParserError)?, )); } Loader::Napi => { @@ -1128,7 +1130,7 @@ pub mod parse_worker { source, b"", )? - .unwrap(), + .ok_or(AnyError::ParserError)?, )); } Loader::Html => { @@ -1154,7 +1156,7 @@ pub mod parse_worker { source, b"", )? - .unwrap(); + .ok_or(AnyError::ParserError)?; ast.import_records = bun_alloc::vec_from_iter_in(import_records, bump); // We're banning import default of html loader files for now. @@ -1280,7 +1282,7 @@ pub mod parse_worker { symbols, ); let _ = temp_log.append_to_maybe_recycled(log, source); - let mut ast = JSAst::init(lazy?.unwrap()); + let mut ast = JSAst::init(lazy?.ok_or(AnyError::ParserError)?); let css_ast_heap = crate::bundled_ast::CssAstRef::from_bump(bump.alloc(css_ast)); ast.css = Some(css_ast_heap); ast.import_records = bun_alloc::vec_from_iter_in(import_records, bump); @@ -1349,7 +1351,7 @@ pub mod parse_worker { source, b"", )? - .unwrap(), + .ok_or(AnyError::ParserError)?, ); ast.add_url_for_css( bump, diff --git a/src/bundler/bundle_v2.rs b/src/bundler/bundle_v2.rs index f9be9d28d01a..b117a316ffa7 100644 --- a/src/bundler/bundle_v2.rs +++ b/src/bundler/bundle_v2.rs @@ -6911,7 +6911,7 @@ pub mod bv2_impl { // We replace this runtime API call's ref later via .link on the Symbol. b"__jsonParse", )? - .unwrap(), + .ok_or(Error::ParserError)?, ); let fake_input_file = crate::Graph::InputFile { diff --git a/src/bundler/transpiler.rs b/src/bundler/transpiler.rs index 536e3ea26598..c0b455f7fa54 100644 --- a/src/bundler/transpiler.rs +++ b/src/bundler/transpiler.rs @@ -1567,6 +1567,7 @@ impl<'a> Transpiler<'a> { lower_import_meta_main_for_node_js: false, framework: None, repl_mode: self.options.repl_mode, + lower_toml_datetimes: false, }; opts.features.emit_decorator_metadata = this_parse.emit_decorator_metadata; diff --git a/src/codegen/cppbind.ts b/src/codegen/cppbind.ts index 210115236420..dedf0324512b 100644 --- a/src/codegen/cppbind.ts +++ b/src/codegen/cppbind.ts @@ -451,6 +451,7 @@ const rustSharedTypes: Record = { // JSC / Bun "BunString": "bun_core::String", + "JSC::TemporalType": "crate::TemporalType", "JSC::EncodedJSValue": "crate::JSValue", "EncodedJSValue": "crate::JSValue", "JSC::JSGlobalObject": "crate::JSGlobalObject", diff --git a/src/js_parser/parse/parse_entry.rs b/src/js_parser/parse/parse_entry.rs index 22ae907b68d7..1d4a4d40bcf9 100644 --- a/src/js_parser/parse/parse_entry.rs +++ b/src/js_parser/parse/parse_entry.rs @@ -104,6 +104,9 @@ pub struct Options<'a> { /// - Wraps last expression in { value: expr } for result capture /// - Wraps code with await in async IIFE pub repl_mode: bool, + + /// Lower `toml_datetime`-tagged strings in a lazy-export AST to `Temporal.*.from` calls. + pub lower_toml_datetimes: bool, } impl<'a> Default for Options<'a> { @@ -135,6 +138,7 @@ impl<'a> Default for Options<'a> { lower_import_meta_main_for_node_js: false, framework: None, repl_mode: false, + lower_toml_datetimes: false, } } } @@ -218,6 +222,7 @@ impl<'a> Options<'a> { lower_import_meta_main_for_node_js: self.lower_import_meta_main_for_node_js, framework: self.framework, repl_mode: self.repl_mode, + lower_toml_datetimes: self.lower_toml_datetimes, } } @@ -289,6 +294,7 @@ impl<'a> Options<'a> { lower_import_meta_main_for_node_js: false, framework: None, repl_mode: false, + lower_toml_datetimes: loader == options::Loader::Toml, }; opts.jsx.parse = loader.is_jsx(); opts @@ -559,6 +565,14 @@ impl<'a> Parser<'a> { let mut final_expr = expr; + // TOML date/time literals become `Temporal.*.from("...")` calls over + // a real unbound symbol, so the chunk renamer reserves the name + // instead of letting a user `Temporal` binding capture it. + if p.options.lower_toml_datetimes { + let mut temporal_ref: Option = None; + lower_date_time_literals(p, &mut final_expr, &mut temporal_ref)?; + } + // Optionally call a runtime API function to transform the expression if !runtime_api_call.is_empty() { let args_slice: &mut [Expr] = p.arena.alloc_slice_fill_with(1, |_| expr); @@ -604,7 +618,110 @@ impl<'a> Parser<'a> { b"", )?)) } +} + +/// A container queued by `lower_date_time_literals`' worklist. +enum DateTimeLowerContainer { + Object(js_ast::StoreRef), + Array(js_ast::StoreRef), +} + +/// Rewrites every `toml_datetime`-tagged `E::String` in `expr` (in place) +/// into a `Temporal..from("")` call, declaring the unbound +/// `Temporal` symbol on first use. The calls are pure-annotated so tree +/// shaking may drop unused exports. Iterative: deep dotted TOML headers nest +/// objects far beyond safe recursion depth. +fn lower_date_time_literals<'a>( + p: &mut JavaScriptParser<'a>, + expr: &mut Expr, + temporal_ref: &mut Option, +) -> Result<(), Error> { + let mut work: Vec = Vec::new(); + lower_one_date_time_literal(p, expr, temporal_ref, &mut work)?; + while let Some(container) = work.pop() { + match container { + DateTimeLowerContainer::Object(mut obj) => { + for property in obj.properties.slice_mut() { + if let Some(value) = &mut property.value { + lower_one_date_time_literal(p, value, temporal_ref, &mut work)?; + } + } + } + DateTimeLowerContainer::Array(mut arr) => { + for item in arr.items.slice_mut() { + lower_one_date_time_literal(p, item, temporal_ref, &mut work)?; + } + } + } + } + Ok(()) +} + +fn lower_one_date_time_literal<'a>( + p: &mut JavaScriptParser<'a>, + expr: &mut Expr, + temporal_ref: &mut Option, + work: &mut Vec, +) -> Result<(), Error> { + match expr.data { + js_ast::ExprData::EString(str) if str.toml_datetime.is_some() => { + let ref_ = match *temporal_ref { + Some(ref_) => ref_, + None => { + let ref_ = + p.declare_common_js_symbol(js_ast::symbol::Kind::Unbound, b"Temporal")?; + *temporal_ref = Some(ref_); + ref_ + } + }; + let (class, text) = { + let str = str.get(); + let kind = str.toml_datetime.expect("infallible: guard checked"); + (kind.temporal_class(), str.slice8()) + }; + let loc = expr.loc; + p.record_usage(ref_); + let namespace = p.new_expr(E::Identifier::init(ref_), loc); + let class_dot = p.new_expr( + E::Dot { + target: namespace, + name: E::Str::new(class), + name_loc: loc, + can_be_removed_if_unused: true, + ..Default::default() + }, + loc, + ); + let from_dot = p.new_expr( + E::Dot { + target: class_dot, + name: E::Str::new(b"from"), + name_loc: loc, + can_be_removed_if_unused: true, + ..Default::default() + }, + loc, + ); + let arg = p.new_expr(E::String::init(text), loc); + let args_slice: &mut [Expr] = p.arena.alloc_slice_fill_with(1, |_| arg); + *expr = p.new_expr( + E::Call { + target: from_dot, + args: Vec::from_arena_slice(args_slice), + can_be_unwrapped_if_unused: E::CallUnwrap::IfUnused, + ..Default::default() + }, + loc, + ); + } + js_ast::ExprData::EArray(arr) => work.push(DateTimeLowerContainer::Array(arr)), + js_ast::ExprData::EObject(obj) => work.push(DateTimeLowerContainer::Object(obj)), + _ => {} + } + Ok(()) +} +impl<'a> Parser<'a> { fn _parse(self) -> Result, Error> { // `Source.path` is `Path<'static>`, so // `path.text` satisfies `Action::Parse(&'static [u8])` directly. diff --git a/src/js_parser_jsc/expr_jsc.rs b/src/js_parser_jsc/expr_jsc.rs index b2921c38e2ed..a4edabe6c97d 100644 --- a/src/js_parser_jsc/expr_jsc.rs +++ b/src/js_parser_jsc/expr_jsc.rs @@ -70,7 +70,12 @@ fn data_to_js_with_check( ExprData::EObject(e) => object_to_js(e, global, stack_check), ExprData::EObjectJSON(e) => object_json_to_js(e, global), ExprData::EArrayJSON(e) => array_json_to_js(e, global), - ExprData::EString(e) => string_to_js(e, global), + ExprData::EString(e) => { + if let Some(kind) = e.toml_datetime { + return toml_datetime_to_js(global, e.slice8(), kind).map_err(js_err); + } + string_to_js(e, global) + } ExprData::ENull(_) => Ok(JSValue::NULL), ExprData::EUndefined(_) => Ok(JSValue::UNDEFINED), ExprData::EBoolean(boolean) | ExprData::EBranchBoolean(boolean) => Ok(if boolean.value { @@ -215,6 +220,25 @@ fn array_json_to_js(this: &E::ArrayJSON, global: &JSGlobalObject) -> Result bun_jsc::JsResult { + debug_assert!(text.is_ascii()); + // SAFETY: `text` is a live slice for the duration of the call. + unsafe { + bun_jsc::cpp::Bun__Temporal__fromDateTimeLiteral( + global, + text.as_ptr(), + text.len(), + kind as u8, + ) + } +} + fn utf8_bytes_to_js(bytes: &[u8], global: &JSGlobalObject) -> Result { if bytes.is_empty() { let empty = BunString::EMPTY; diff --git a/src/js_parser_jsc/lib.rs b/src/js_parser_jsc/lib.rs index 74a4a72b11a1..31cfb03c2335 100644 --- a/src/js_parser_jsc/lib.rs +++ b/src/js_parser_jsc/lib.rs @@ -12,5 +12,6 @@ pub mod expr_jsc; // callers can write `bun_js_parser_jsc::Expr` / `expr.to_js(global)` without // also depending on `bun_js_parser` directly. pub use expr_jsc::{ - ExprJsc, data_to_js, expr_to_js, string_to_js, to_js_error, value_string_to_js, + ExprJsc, data_to_js, expr_to_js, string_to_js, to_js_error, toml_datetime_to_js, + value_string_to_js, }; diff --git a/src/js_printer/lib.rs b/src/js_printer/lib.rs index 58c3c2dae721..68ef9a6b2e9f 100644 --- a/src/js_printer/lib.rs +++ b/src/js_printer/lib.rs @@ -3740,6 +3740,27 @@ pub(crate) mod __gated_printer { } } ExprData::EString(e) => { + // The `--no-bundle` data-loader path prints the TOML AST + // as-is; the bundler lowers these to a real call first. + if let Some(kind) = e.toml_datetime { + let wrap = level.gte(Level::New) || flags.contains(ExprFlag::ForbidCall); + if wrap { + self.print(b"("); + } + self.print_space_before_identifier(); + self.add_source_mapping(expr.loc); + self.print(b"Temporal."); + self.print(kind.temporal_class()); + self.print(b".from(\""); + // Always ASCII (validated by the TOML scanner); no escaping. + self.print(e.slice8()); + self.print(b"\")"); + if wrap { + self.print(b")"); + } + return; + } + let mut e = *e; e.resolve_rope_if_needed(self.bump); self.add_source_mapping(expr.loc); diff --git a/src/jsc/JSValue.rs b/src/jsc/JSValue.rs index c4b8ab3f5c20..018b1312a480 100644 --- a/src/jsc/JSValue.rs +++ b/src/jsc/JSValue.rs @@ -1587,6 +1587,10 @@ impl JSValue { JSC__JSValue__jsonStringifyFast(self, global, out) }) } + + pub fn temporal_type(self) -> TemporalType { + crate::cpp::Bun__JSValue__temporalType(self) + } } // ────────────────────────────────────────────────────────────────────────── @@ -2139,6 +2143,21 @@ pub enum ProxyField { Handler = 1, } +/// `JSC::TemporalType` (TemporalObject.h) — result of [`JSValue::temporal_type`]. +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TemporalType { + None = 0, + Instant = 1, + PlainDateTime = 2, + PlainDate = 3, + PlainTime = 4, + ZonedDateTime = 5, + PlainYearMonth = 6, + PlainMonthDay = 7, + Duration = 8, +} + /// `JSValue.SerializedFlags`. #[derive(Debug, Default, Clone, Copy)] pub struct SerializedFlags { diff --git a/src/jsc/bindings/bindings.cpp b/src/jsc/bindings/bindings.cpp index ae5b28e37b6c..62ca62606a17 100644 --- a/src/jsc/bindings/bindings.cpp +++ b/src/jsc/bindings/bindings.cpp @@ -98,6 +98,7 @@ #include "JavaScriptCore/IntlObject.h" #include "JavaScriptCore/ISO8601.h" #include "JavaScriptCore/JSCTimeZone.h" +#include "JavaScriptCore/InstantCore.h" #include "JavaScriptCore/TemporalCoreTypes.h" #include "JavaScriptCore/TemporalDuration.h" #include "JavaScriptCore/TemporalEnums.h" @@ -108,6 +109,7 @@ #include "JavaScriptCore/TemporalPlainTime.h" #include "JavaScriptCore/TemporalPlainYearMonth.h" #include "JavaScriptCore/TemporalZonedDateTime.h" +#include "JavaScriptCore/TemporalObject.h" #include "JavaScriptCore/TimeZoneICUBridge.h" #include "JavaScriptCore/FunctionPrototype.h" @@ -6169,6 +6171,159 @@ extern "C" [[ZIG_EXPORT(nothrow)]] double Bun__gregorianDateTimeToMSInZone(JSC:: return static_cast(r->epochMilliseconds()); } +// Materializes a date/time literal as a Temporal object through the same +// paths `Temporal.*.from(string)` takes. `kind` mirrors the Rust +// `bun_ast::E::TomlDateTimeKind` discriminants. +extern "C" [[ZIG_EXPORT(zero_is_throw)]] EncodedJSValue Bun__Temporal__fromDateTimeLiteral(JSC::JSGlobalObject* globalObject, const uint8_t* text, size_t len, uint8_t kind) +{ + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + // The Temporal structures on the global object only exist when the + // option is on; reaching for them would crash. + if (!JSC::Options::useTemporal()) [[unlikely]] { + JSC::throwTypeError(globalObject, scope, "Date/time values require Temporal, which is disabled in this process"_s); + return {}; + } + + WTF::String string { std::span(reinterpret_cast(text), len) }; + JSC::JSValue item = JSC::jsString(vm, string); + + JSC::JSObject* result = nullptr; + switch (kind) { + case 1: + result = JSC::TemporalInstant::toInstant(globalObject, item); + break; + case 2: + result = JSC::TemporalPlainDateTime::from(globalObject, item, JSC::jsUndefined()); + break; + case 3: + result = JSC::TemporalPlainDate::from(globalObject, item, JSC::jsUndefined()); + break; + case 4: + result = JSC::TemporalPlainTime::from(globalObject, item, JSC::jsUndefined()); + break; + default: + RELEASE_ASSERT_NOT_REACHED(); + } + RETURN_IF_EXCEPTION(scope, {}); + ASSERT(result); + return JSValue::encode(result); +} + +extern "C" [[ZIG_EXPORT(nothrow)]] JSC::TemporalType Bun__JSValue__temporalType(JSC::EncodedJSValue encodedValue) +{ + return JSC::temporalType(JSC::JSValue::decode(encodedValue)); +} + +static Int128 ceilToMultiple(Int128 ns, Int128 unit) +{ + Int128 rem = ns % unit; + return rem == 0 ? ns : ns - rem + (ns > 0 ? unit : 0); +} + +static Int128 floorToMultiple(Int128 ns, Int128 unit) +{ + Int128 rem = ns % unit; + return rem == 0 ? ns : ns - rem - (ns < 0 ? unit : 0); +} + +// The `±HH:MM` offset to spell `exactTime` with so its local year has TOML's +// four digits: `preferredNs` if that fits, else the closest whole-hour (then +// whole-minute) offset that does; nullopt if none within ±23:59 does. +static std::optional tomlOffsetForInstant(JSC::ISO8601::ExactTime exactTime, int64_t preferredNs) +{ + using JSC::ISO8601::ExactTime; + constexpr Int128 minLocal = Int128 { -62167219200 } * ExactTime::nsPerSecond; // 0000-01-01T00:00:00 + constexpr Int128 maxLocal = Int128 { 253402300800 } * ExactTime::nsPerSecond; // +010000-01-01T00:00:00 + constexpr Int128 maxOffset = ExactTime::nsPerHour * 23 + ExactTime::nsPerMinute * 59; + + Int128 epoch = exactTime.epochNanoseconds(); + // Whole-minute offsets o with minLocal <= epoch + o < maxLocal. + Int128 lo = std::max(ceilToMultiple(minLocal - epoch, ExactTime::nsPerMinute), -maxOffset); + Int128 hi = std::min(floorToMultiple(maxLocal - Int128 { 1 } - epoch, ExactTime::nsPerMinute), maxOffset); + if (lo > hi) + return std::nullopt; + Int128 preferred { preferredNs }; + if (preferred < lo) { + Int128 hour = ceilToMultiple(lo, ExactTime::nsPerHour); + return static_cast(hour <= hi ? hour : lo); + } + if (preferred > hi) { + Int128 hour = floorToMultiple(hi, ExactTime::nsPerHour); + return static_cast(hour >= lo ? hour : hi); + } + return preferredNs; +} + +// Formats a Temporal object as a TOML date/time literal into `buf` and +// returns the length written, or -1 if its year is outside TOML's +// 0000..9999. The `[u-ca=...]` and `[Time/Zone]` annotations, which TOML +// cannot carry, are dropped. +extern "C" [[ZIG_EXPORT(check_slow)]] int32_t Bun__Temporal__toTOMLDateTime(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue encodedValue, JSC::TemporalType temporalType, uint8_t* buf, size_t bufLen) +{ + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSC::JSCell* cell = JSC::JSValue::decode(encodedValue).asCell(); + constexpr JSC::PrecisionData autoPrecision { { JSC::Precision::Auto, 0 }, JSC::TemporalUnit::Nanosecond, 1 }; + + WTF::String string; + switch (temporalType) { + case JSC::TemporalType::Instant: { + auto exactTime = uncheckedDowncast(cell)->exactTime(); + std::optional offsetNs = tomlOffsetForInstant(exactTime, 0); + if (!offsetNs) + return -1; + if (!*offsetNs) + offsetNs = std::nullopt; // `Z` + string = JSC::TemporalCore::instantToString(exactTime, offsetNs, autoPrecision); + break; + } + case JSC::TemporalType::PlainDateTime: { + auto* dateTime = uncheckedDowncast(cell); + string = JSC::ISO8601::temporalDateTimeToString(dateTime->plainDate(), dateTime->plainTime(), { JSC::Precision::Auto, 0 }); + break; + } + case JSC::TemporalType::PlainDate: + string = JSC::ISO8601::temporalDateToString(uncheckedDowncast(cell)->plainDate()); + break; + case JSC::TemporalType::PlainTime: + string = JSC::ISO8601::temporalTimeToString(uncheckedDowncast(cell)->plainTime(), { JSC::Precision::Auto, 0 }); + break; + case JSC::TemporalType::ZonedDateTime: { + auto* zoned = uncheckedDowncast(cell); + std::optional zoneOffsetNs = zoned->getOffsetNanoseconds(globalObject); + RETURN_IF_EXCEPTION(scope, 0); + ASSERT(zoneOffsetNs); + // TOML offsets are `HH:MM`; a historic sub-minute (LMT) offset is + // spelled as `Z` instead. + bool wholeMinutes = *zoneOffsetNs % 60000000000ll == 0; + std::optional offsetNs = tomlOffsetForInstant(zoned->exactTime(), wholeMinutes ? *zoneOffsetNs : 0); + if (!offsetNs) + return -1; + if (!wholeMinutes && !*offsetNs) + offsetNs = std::nullopt; + string = JSC::TemporalCore::instantToString(zoned->exactTime(), offsetNs, autoPrecision); + break; + } + default: + RELEASE_ASSERT_NOT_REACHED(); + } + + // The expanded-year form of a PlainDate/PlainDateTime (`+010000-…`, `-000001-…`). + if (!isASCIIDigit(string[0])) + return -1; + + unsigned length = string.length(); + RELEASE_ASSERT(length <= bufLen); + for (unsigned i = 0; i < length; i++) { + ASSERT(isASCII(string[i])); + buf[i] = static_cast(string[i]); + } + return static_cast(length); +} + extern "C" EncodedJSValue JSC__JSValue__dateInstanceFromNumber(JSC::JSGlobalObject* globalObject, double unixTimestamp) { auto& vm = JSC::getVM(globalObject); diff --git a/src/jsc/lib.rs b/src/jsc/lib.rs index 752d17f49301..605f0c9e9e16 100644 --- a/src/jsc/lib.rs +++ b/src/jsc/lib.rs @@ -180,6 +180,7 @@ pub mod zig_string; pub use self::js_value::{ CoerceTo, ComparisonResult, ForEachCallback, FromAny, FromJsEnum, JSValue, Protected as ProtectedJSValue, ProxyField, SerializedFlags, SerializedScriptValue, + TemporalType, }; // LAYERING (PORTING.md §Dispatch): the task dispatch covers every concrete diff --git a/src/parsers/toml.rs b/src/parsers/toml.rs index aff96cfad4fb..ac0fbffc411e 100644 --- a/src/parsers/toml.rs +++ b/src/parsers/toml.rs @@ -16,7 +16,10 @@ //! - integers parse as `f64` but are validated as 64-bit integers first; //! values outside `Number.MAX_SAFE_INTEGER` are errors (TOML requires //! lossless handling or an error) -//! - date/time values (all four kinds) become strings of their source text +//! - date/time values become `E::String`s tagged with `toml_datetime`, which +//! materialize as Temporal objects (offset date-time → `Temporal.Instant`, +//! local date-time → `Temporal.PlainDateTime`, local date → +//! `Temporal.PlainDate`, local time → `Temporal.PlainTime`) //! - strings are UTF-8; non-ASCII content is re-encoded to UTF-16 EStrings //! so both the JS conversion and the printer paths agree @@ -133,8 +136,11 @@ enum ValueData<'a> { is_ascii: bool, }, Number(f64), - /// All four TOML date/time kinds, as their source text (always ASCII). - DateTime(&'a [u8]), + /// One of the four TOML date/time kinds, as its source text (always ASCII). + DateTime { + text: &'a [u8], + kind: E::TomlDateTimeKind, + }, Boolean(bool), ArrayOpen, InlineOpen, @@ -184,6 +190,28 @@ const MAX_SAFE_INTEGER: i64 = (1 << 53) - 1; const BARE_CR: &[u8] = b"Bare carriage return is not allowed; use \\r\\n or \\n"; const UNDERSCORE_IN_NUMBER: &[u8] = b"Underscores in numbers must be surrounded by digits"; +/// TOML says excess fractional-second precision "should be truncated, not +/// rounded"; Temporal rejects more than its 9 digits, so drop the rest here. +fn truncate_fractional_seconds<'a>(text: &'a [u8], bump: &'a Bump) -> &'a [u8] { + let Some(dot) = bun_core::strings::index_of_char_usize(text, b'.') else { + return text; + }; + let frac_start = dot + 1; + let mut frac_end = frac_start; + while frac_end < text.len() && text[frac_end].is_ascii_digit() { + frac_end += 1; + } + if frac_end - frac_start <= 9 { + return text; + } + let keep = frac_start + 9; + let mut out: ArenaVec<'a, u8> = + ArenaVec::with_capacity_in(keep + (text.len() - frac_end), bump); + out.extend_from_slice(&text[..keep]); + out.extend_from_slice(&text[frac_end..]); + out.into_bump_slice() +} + fn is_bare_key_char(c: u8) -> bool { c.is_ascii_alphanumeric() || c == b'-' || c == b'_' } @@ -634,15 +662,18 @@ impl<'a, 'log> Scanner<'a, 'log> { if self.peek().is_ascii_digit() { let d1 = self.digit_run_len(self.pos); if d1 == 4 && self.peek_at(self.pos + 4) == b'-' { - let text = self.scan_datetime_from_date()?; + let (text, kind) = self.scan_datetime_from_date()?; self.expect_value_terminator()?; - return Ok(ValueData::DateTime(text)); + return Ok(ValueData::DateTime { text, kind }); } if d1 == 2 && self.peek_at(self.pos + 2) == b':' { let start = self.pos; self.scan_time_digits()?; self.expect_value_terminator()?; - return Ok(ValueData::DateTime(&self.src[start..self.pos])); + return Ok(ValueData::DateTime { + text: &self.src[start..self.pos], + kind: E::TomlDateTimeKind::LocalTime, + }); } } @@ -672,8 +703,8 @@ impl<'a, 'log> Scanner<'a, 'log> { } /// `YYYY-MM-DD` and everything that may follow it (time, offset). - /// Returns the full source text of the literal. - fn scan_datetime_from_date(&mut self) -> PResult<&'a [u8]> { + /// Returns the full source text of the literal and which kind it is. + fn scan_datetime_from_date(&mut self) -> PResult<(&'a [u8], E::TomlDateTimeKind)> { let start = self.pos; let year = self.read_digits(4, b"Invalid date: expected a 4-digit year")?; @@ -724,44 +755,53 @@ impl<'a, 'log> Scanner<'a, 'log> { _ => false, }; - if has_time { - self.scan_time_digits()?; - // Optional offset. - match self.peek() { - b'Z' | b'z' => { - self.pos += 1; + if !has_time { + return Ok((&self.src[start..self.pos], E::TomlDateTimeKind::LocalDate)); + } + + self.scan_time_digits()?; + // Optional offset. + let has_offset = match self.peek() { + b'Z' | b'z' => { + self.pos += 1; + true + } + b'+' | b'-' => { + self.pos += 1; + let hour = + self.read_digits(2, b"Invalid date-time offset: expected 2-digit hours")?; + if self.peek() != b':' { + return Err(self.err( + self.pos, + b"Invalid date-time offset: expected ':' between hours and minutes", + )); } - b'+' | b'-' => { - self.pos += 1; - let hour = - self.read_digits(2, b"Invalid date-time offset: expected 2-digit hours")?; - if self.peek() != b':' { - return Err(self.err( - self.pos, - b"Invalid date-time offset: expected ':' between hours and minutes", - )); - } - self.pos += 1; - let minute = - self.read_digits(2, b"Invalid date-time offset: expected 2-digit minutes")?; - if hour > 23 { - return Err(self.err( - start, - b"Invalid date-time offset: hours must be between 00 and 23", - )); - } - if minute > 59 { - return Err(self.err( - start, - b"Invalid date-time offset: minutes must be between 00 and 59", - )); - } + self.pos += 1; + let minute = + self.read_digits(2, b"Invalid date-time offset: expected 2-digit minutes")?; + if hour > 23 { + return Err(self.err( + start, + b"Invalid date-time offset: hours must be between 00 and 23", + )); } - _ => {} + if minute > 59 { + return Err(self.err( + start, + b"Invalid date-time offset: minutes must be between 00 and 59", + )); + } + true } - } + _ => false, + }; - Ok(&self.src[start..self.pos]) + let kind = if has_offset { + E::TomlDateTimeKind::OffsetDateTime + } else { + E::TomlDateTimeKind::LocalDateTime + }; + Ok((&self.src[start..self.pos], kind)) } /// `HH:MM[:SS[.frac]]` — seconds are optional in TOML 1.1. @@ -1713,7 +1753,10 @@ impl<'a, 'log> Parser<'a, 'log> { match token.data { ValueData::String { text, is_ascii } => Ok(self.string_expr(text, is_ascii, loc)), ValueData::Number(n) => Ok(Expr::init(E::Number::new(n), loc)), - ValueData::DateTime(text) => Ok(Expr::init(E::String::init(text), loc)), + ValueData::DateTime { text, kind } => { + let text = truncate_fractional_seconds(text, self.bump); + Ok(Expr::init(E::String::init_toml_datetime(text, kind), loc)) + } ValueData::Boolean(b) => Ok(Expr::init(E::Boolean { value: b }, loc)), ValueData::ArrayOpen => self.parse_array(token.pos), ValueData::InlineOpen => self.parse_inline_table(token.pos), diff --git a/src/runtime/api.rs b/src/runtime/api.rs index 76bf14fbf80a..680029fafff1 100644 --- a/src/runtime/api.rs +++ b/src/runtime/api.rs @@ -345,66 +345,13 @@ fn with_text_format_source_encoded( // ─── shared Expr → JS conversion for the text-format parsers ───────────────── -fn estring_to_js( - str: &bun_ast::E::EString, - global: &bun_jsc::JSGlobalObject, -) -> bun_jsc::JsResult { - use bun_jsc::StringJsc as _; - // NOTE: the text-format parsers never build ropes, so the simple - // slice → JS path is sufficient. - if str.is_utf16 { - let zig = bun_core::ZigString::init_utf16(str.slice16()); - let bun_s = bun_core::String::init(zig); - bun_s.to_js(global) - } else { - bun_jsc::bun_string_jsc::create_utf8_for_js(global, str.slice8()) - } -} - +/// `Expr` → `JSValue` for the text-format parsers (TOML, JSON5), through the +/// same converter the module loader uses for imported data files, so +/// `Bun.TOML.parse` and `import "./x.toml"` cannot drift apart. fn expr_to_js( expr: bun_ast::Expr, global: &bun_jsc::JSGlobalObject, ) -> bun_jsc::JsResult { - expr_to_js_with_check(expr, global, bun_core::StackCheck::init()) -} - -fn expr_to_js_with_check( - expr: bun_ast::Expr, - global: &bun_jsc::JSGlobalObject, - stack_check: bun_core::StackCheck, -) -> bun_jsc::JsResult { - use bun_ast::expr::Data as ExprData; - use bun_collections::VecExt as _; - use bun_jsc::JSValue; - - if !stack_check.is_safe_to_recurse() { - return Err(global.throw_stack_overflow()); - } - match expr.data { - ExprData::ENull(_) => Ok(JSValue::NULL), - ExprData::EBoolean(boolean) => Ok(JSValue::from(boolean.value)), - ExprData::ENumber(number) => Ok(JSValue::js_number(number.value())), - ExprData::EString(str) => estring_to_js(str.get(), global), - ExprData::EArray(arr) => { - JSValue::create_array_from_iter(global, arr.slice().iter(), |item| { - expr_to_js_with_check(*item, global, stack_check) - }) - } - ExprData::EObject(obj) => { - let js_obj = JSValue::create_empty_object(global, obj.properties.len_u32() as usize); - for prop in obj.properties.slice() { - let key_expr = prop.key.expect("infallible: prop has key"); - let value = expr_to_js_with_check( - prop.value.expect("infallible: prop has value"), - global, - stack_check, - )?; - let key_js = expr_to_js_with_check(key_expr, global, stack_check)?; - let key_str = bun_core::OwnedString::new(key_js.to_bun_string(global)?); - js_obj.put_may_be_index(global, &key_str, value)?; - } - Ok(js_obj) - } - _ => Ok(JSValue::UNDEFINED), - } + bun_js_parser_jsc::expr_to_js(&expr, global) + .map_err(|e| bun_js_parser_jsc::to_js_error(e, global)) } diff --git a/src/runtime/api/TOMLObject.rs b/src/runtime/api/TOMLObject.rs index 1dc12136f876..5ae7cb4409e6 100644 --- a/src/runtime/api/TOMLObject.rs +++ b/src/runtime/api/TOMLObject.rs @@ -1,7 +1,9 @@ use bun_collections::HashMap; use bun_core::StackCheck; use bun_core::{OwnedString, String as BunString}; -use bun_jsc::{self as jsc, CallFrame, JSGlobalObject, JSValue, JsError, JsResult, wtf}; +use bun_jsc::{ + self as jsc, CallFrame, JSGlobalObject, JSValue, JsError, JsResult, TemporalType, wtf, +}; use bun_parsers::toml::TOML; pub(crate) fn create(global: &JSGlobalObject) -> JSValue { @@ -70,7 +72,11 @@ fn stringify(global: &JSGlobalObject, frame: &CallFrame) -> JsResult { } let unwrapped = value.unwrap_boxed_primitive(global)?; - if !unwrapped.is_object() || unwrapped.is_array() || unwrapped.is_date() { + if !unwrapped.is_object() + || unwrapped.is_array() + || unwrapped.is_date() + || temporal_object_type(unwrapped).is_some() + { return Err(global.throw(format_args!( "TOML.stringify expects an object at the top level (a TOML document is a table)" ))); @@ -116,6 +122,9 @@ const MAX_SAFE_INTEGER_F: f64 = 9007199254740991.0; enum Layout { /// `key = value` on the current table's line block. Keyval, + /// `key = value` whose value is a Temporal object; carries the + /// classification so emission does not re-ask. + TemporalKeyval(TemporalType), /// `[path.key]` section. Table, /// `[[path.key]]` section per element. @@ -176,13 +185,21 @@ impl Stringifier { } while let Some(item) = iter.next()? { let item = item.unwrap_boxed_primitive(global)?; - if !item.is_object() || item.is_array() || item.is_date() || item.is_function() { + if !item.is_object() + || item.is_array() + || item.is_date() + || item.is_function() + || temporal_object_type(item).is_some() + { return Ok(Layout::Keyval); } } return Ok(Layout::ArrayOfTables); } if value.is_object() && !value.is_date() { + if let Some(temporal_type) = temporal_object_type(value) { + return Ok(Layout::TemporalKeyval(temporal_type)); + } return Ok(Layout::Table); } Ok(Layout::Keyval) @@ -217,17 +234,20 @@ impl Stringifier { if value.is_null() { return Err(self.err_null_value(global, &prop_name)); } - if let Layout::Keyval = self.layout_of(global, value)? { - if header_pending { - header_pending = false; - self.append_header(false); - } - self.append_key_segment(&prop_name); - self.builder.append_latin1(b" = "); - self.stringify_inline_value(global, value)?; - self.builder.append_lchar(b'\n'); - self.wrote = true; + let known_temporal = match self.layout_of(global, value)? { + Layout::Keyval => None, + Layout::TemporalKeyval(temporal_type) => Some(temporal_type), + Layout::Table | Layout::ArrayOfTables | Layout::Skip => continue, + }; + if header_pending { + header_pending = false; + self.append_header(false); } + self.append_key_segment(&prop_name); + self.builder.append_latin1(b" = "); + self.stringify_inline_value(global, value, known_temporal)?; + self.builder.append_lchar(b'\n'); + self.wrote = true; } // Pass 2: sections. Values are re-read; an array-of-tables element @@ -237,7 +257,7 @@ impl Stringifier { while let Some(prop_name) = iter.next()? { let value = iter.value.unwrap_boxed_primitive(global)?; match self.layout_of(global, value)? { - Layout::Keyval | Layout::Skip => {} + Layout::Keyval | Layout::TemporalKeyval(_) | Layout::Skip => {} Layout::Table => { header_pending = false; self.mark_visiting(global, value)?; @@ -257,6 +277,7 @@ impl Stringifier { || item.is_array() || item.is_date() || item.is_function() + || temporal_object_type(item).is_some() { self.path.pop(); return Err(self.err_changed(global)); @@ -281,11 +302,13 @@ impl Stringifier { } /// One value on the right-hand side of `=` (or inside an inline - /// array/table). `value` is already unboxed. + /// array/table). `value` is already unboxed; `known_temporal` is the + /// classification `layout_of` already computed for it, if any. fn stringify_inline_value( &mut self, global: &JSGlobalObject, value: JSValue, + known_temporal: Option, ) -> StringifyResult<()> { if !self.stack_check.is_safe_to_recurse() { return Err(StringifyError::StackOverflow); @@ -321,6 +344,10 @@ impl Stringifier { return self.append_datetime(global, value); } + if let Some(temporal_type) = known_temporal.or_else(|| temporal_object_type(value)) { + return self.append_temporal(global, value, temporal_type); + } + if value.is_array() { self.mark_visiting(global, value)?; self.builder.append_lchar(b'['); @@ -335,7 +362,7 @@ impl Stringifier { if item.is_null() || item.is_undefined() || item.is_symbol() || item.is_function() { return Err(self.err_in_array(global, item)); } - self.stringify_inline_value(global, item)?; + self.stringify_inline_value(global, item, None)?; } self.builder.append_lchar(b']'); self.visiting.remove(&value); @@ -367,7 +394,7 @@ impl Stringifier { first = false; self.append_key_segment(&prop_name); self.builder.append_latin1(b" = "); - self.stringify_inline_value(global, prop_value)?; + self.stringify_inline_value(global, prop_value, None)?; } self.builder .append_latin1(if first { b"{}" } else { b" }" }); @@ -469,7 +496,58 @@ impl Stringifier { )) .into()); } - self.builder.append_latin1(iso); + // `toISOString` always prints three fraction digits; trim trailing + // zeros (and a bare `.`) so `Date` and `Temporal.Instant` spell the + // same instant identically. + debug_assert!(iso.len() == 24 && iso[19] == b'.' && iso[23] == b'Z'); + let mut end = 23; + while end > 20 && iso[end - 1] == b'0' { + end -= 1; + } + if end == 20 { + end = 19; + } + self.builder.append_latin1(&iso[..end]); + self.builder.append_lchar(b'Z'); + Ok(()) + } + + /// A Temporal object as the TOML date/time literal of its type; + /// `PlainYearMonth`/`PlainMonthDay`/`Duration` have no TOML form and throw. + fn append_temporal( + &mut self, + global: &JSGlobalObject, + value: JSValue, + temporal_type: TemporalType, + ) -> StringifyResult<()> { + if !has_toml_form(temporal_type) { + return Err(global + .throw(format_args!( + "TOML.stringify cannot serialize {} (it has no TOML representation)", + temporal_name(temporal_type) + )) + .into()); + } + let mut buf = [0u8; 64]; + // SAFETY: `buf` is a live stack buffer for the duration of the call. + let len = unsafe { + jsc::cpp::Bun__Temporal__toTOMLDateTime( + global, + value, + temporal_type, + buf.as_mut_ptr(), + buf.len(), + ) + }?; + if len < 0 { + return Err(global + .throw(format_args!( + "TOML.stringify cannot serialize a {} outside years 0000-9999", + temporal_name(temporal_type) + )) + .into()); + } + self.builder.append_latin1(&buf[..len as usize]); Ok(()) } @@ -509,6 +587,42 @@ impl Stringifier { } } +fn temporal_object_type(value: JSValue) -> Option { + match value.temporal_type() { + TemporalType::None => None, + t => Some(t), + } +} + +/// Whether TOML has a date/time literal for this type. +fn has_toml_form(t: TemporalType) -> bool { + match t { + TemporalType::Instant + | TemporalType::PlainDateTime + | TemporalType::PlainDate + | TemporalType::PlainTime + | TemporalType::ZonedDateTime => true, + TemporalType::None + | TemporalType::PlainYearMonth + | TemporalType::PlainMonthDay + | TemporalType::Duration => false, + } +} + +fn temporal_name(t: TemporalType) -> &'static str { + match t { + TemporalType::Instant => "Temporal.Instant", + TemporalType::PlainDateTime => "Temporal.PlainDateTime", + TemporalType::PlainDate => "Temporal.PlainDate", + TemporalType::PlainTime => "Temporal.PlainTime", + TemporalType::ZonedDateTime => "Temporal.ZonedDateTime", + TemporalType::PlainYearMonth => "Temporal.PlainYearMonth", + TemporalType::PlainMonthDay => "Temporal.PlainMonthDay", + TemporalType::Duration => "Temporal.Duration", + TemporalType::None => unreachable!("not a Temporal object"), + } +} + fn is_bare_key(name: &BunString) -> bool { if name.length() == 0 { return false; diff --git a/test/bundler/bundler_loader.test.ts b/test/bundler/bundler_loader.test.ts index 72dfc0e353dd..14e278487608 100644 --- a/test/bundler/bundler_loader.test.ts +++ b/test/bundler/bundler_loader.test.ts @@ -54,6 +54,73 @@ describe("bundler", async () => { }, run: { stdout: '{"hello":"world"}' }, }); + // The Temporal reference is a real unbound symbol: a user binding named + // Temporal in the same bundle gets renamed instead of capturing the + // `Temporal.*.from` calls the TOML module compiles to. + itBundled("bun/loader-toml-datetime-shadowed-temporal-global", { + target, + files: { + "/entry.ts": /* js */ ` + import cfg from './config.toml'; + var Temporal = "shadowed"; + console.write(Temporal + " " + cfg.ld.toString()); + `, + "/config.toml": `ld = 1979-05-27`, + }, + run: { stdout: "shadowed 1979-05-27" }, + }); + // The realistic collision: another module in the chunk imports a + // Temporal polyfill binding. The import gets renamed and the TOML + // module's calls still resolve to the native global. + itBundled("bun/loader-toml-datetime-imported-temporal-binding", { + target, + files: { + "/entry.ts": /* js */ ` + import { Temporal } from './polyfill.js'; + import cfg from './config.toml'; + console.write(Temporal.tag + " " + (cfg.ld instanceof globalThis.Temporal.PlainDate) + " " + cfg.ld.toString()); + `, + "/polyfill.js": `export const Temporal = { tag: "polyfill" };`, + "/config.toml": `ld = 1979-05-27`, + }, + run: { stdout: "polyfill true 1979-05-27" }, + }); + itBundled("bun/loader-toml-datetime-no-bundle", { + target, + bundling: false, + entryPoints: ["/config.toml"], + files: { + "/config.toml": `d = 1979-05-27\n[t]\nat = 1979-05-27T00:32:00-07:00`, + }, + run: true, + onAfterBundle(api) { + const code = api.readFile("/out.js"); + expect(code).toContain('Temporal.PlainDate.from("1979-05-27")'); + expect(code).toContain('Temporal.Instant.from("1979-05-27T00:32:00-07:00")'); + }, + }); + // TOML date/time values bundle as Temporal construction calls; the + // bundled module yields the same values Bun.TOML.parse returns. + itBundled("bun/loader-toml-datetime", { + target, + files: { + "/entry.ts": /* js */ ` + import cfg, { lt } from './config.toml'; + console.write(JSON.stringify([ + cfg.odt instanceof Temporal.Instant, cfg.odt.toString(), + cfg.ldt instanceof Temporal.PlainDateTime, cfg.ldt.toString(), + cfg.ld instanceof Temporal.PlainDate, cfg.ld.toString(), + lt instanceof Temporal.PlainTime, lt.toString(), + cfg.tbl.arr[0].toString(), + ])); + `, + "/config.toml": `odt = 1979-05-27T00:32:00-07:00\nldt = 1979-05-27 07:32\nld = 1979-05-27\nlt = 07:32:00.500\n[tbl]\narr = [ 07:32:00 ]`, + }, + run: { + stdout: + '[true,"1979-05-27T07:32:00Z",true,"1979-05-27T07:32:00",true,"1979-05-27",true,"07:32:00.5","07:32:00"]', + }, + }); itBundled("bun/loader-text-file", { target, files: { diff --git a/test/js/bun/resolve/toml/toml-fixture.toml b/test/js/bun/resolve/toml/toml-fixture.toml index bed28762b3e9..20ce461d7d0b 100644 --- a/test/js/bun/resolve/toml/toml-fixture.toml +++ b/test/js/bun/resolve/toml/toml-fixture.toml @@ -40,3 +40,9 @@ entry_one = "three" [[array.nested]] entry_one = "four" + +[dates] +odt = 1979-05-27T00:32:00-07:00 +ldt = 1979-05-27T07:32:00 +ld = 1979-05-27 +lt = 07:32:00 diff --git a/test/js/bun/resolve/toml/toml-fixture.toml.txt b/test/js/bun/resolve/toml/toml-fixture.toml.txt index 5b7df33af2b5..7154794b410e 100644 --- a/test/js/bun/resolve/toml/toml-fixture.toml.txt +++ b/test/js/bun/resolve/toml/toml-fixture.toml.txt @@ -40,3 +40,9 @@ entry_one = "three" [[array.nested]] entry_one = "four" + +[dates] +odt = 1979-05-27T00:32:00-07:00 +ldt = 1979-05-27T07:32:00 +ld = 1979-05-27 +lt = 07:32:00 diff --git a/test/js/bun/resolve/toml/toml.test.js b/test/js/bun/resolve/toml/toml.test.js index 18c307078af7..112c15a2b54c 100644 --- a/test/js/bun/resolve/toml/toml.test.js +++ b/test/js/bun/resolve/toml/toml.test.js @@ -21,6 +21,15 @@ function checkToml(toml) { expect(toml.install.scopes["@mybigcompany3"].three).toBe(4); expect(toml.install.cache.dir).toBe("C:\\Windows\\System32"); expect(toml.install.cache.dir2).toBe("C:\\Windows\\System32\\🏳️‍🌈"); + // Imported date/time values are the same Temporal objects TOML.parse returns. + expect(toml.dates.odt).toBeInstanceOf(Temporal.Instant); + expect(toml.dates.odt.toString()).toBe("1979-05-27T07:32:00Z"); + expect(toml.dates.ldt).toBeInstanceOf(Temporal.PlainDateTime); + expect(toml.dates.ldt.toString()).toBe("1979-05-27T07:32:00"); + expect(toml.dates.ld).toBeInstanceOf(Temporal.PlainDate); + expect(toml.dates.ld.toString()).toBe("1979-05-27"); + expect(toml.dates.lt).toBeInstanceOf(Temporal.PlainTime); + expect(toml.dates.lt.toString()).toBe("07:32:00"); } it("via dynamic import", async () => { diff --git a/test/js/bun/toml/generate_toml_test_suite.ts b/test/js/bun/toml/generate_toml_test_suite.ts index e0ba0b62c358..3559e30d2403 100644 --- a/test/js/bun/toml/generate_toml_test_suite.ts +++ b/test/js/bun/toml/generate_toml_test_suite.ts @@ -18,8 +18,10 @@ * - integers -> number; values outside Number.MAX_SAFE_INTEGER throw * (TOML requires lossless handling or an error; mixed number/BigInt * output is not acceptable API) - * - datetime/datetime-local/date-local/time-local -> string (source text), - * compared via separator/fraction normalization (see generated helper) + * - datetime -> Temporal.Instant, datetime-local -> Temporal.PlainDateTime, + * date-local -> Temporal.PlainDate, time-local -> Temporal.PlainTime; + * compared with plain toEqual (deepEquals compares Temporal objects by + * class and value) * - invalid documents -> SyntaxError, with the exact full message asserted * when the in-tree parser produced a SyntaxError at generation time */ @@ -243,8 +245,6 @@ function valueToJS(val: unknown, indent: number = 0): string { // --------------------------------------------------------------------------- // 6. Generate the test file // --------------------------------------------------------------------------- -const kindUnion = DATETIME_KINDS.map(k => JSON.stringify(k)).join(" | "); - let output = `// Tests generated from the official toml-lang/toml-test conformance suite // Generated from toml-test commit: ${commit} // Scope: TOML v1.1.0 manifest (tests/files-toml-1.1.0): ${validCases.length} valid + ${outOfRangeCases.length} out-of-range-integer + ${invalidCases.length} invalid + ${invalidEncodingCases.length} invalid-encoding cases @@ -253,10 +253,10 @@ let output = `// Tests generated from the official toml-lang/toml-test conforman // TOML type encoding asserted by these tests: // - integer: number; values outside Number.MAX_SAFE_INTEGER throw (TOML // requires lossless handling or an error — see the out-of-range block) -// - datetime, datetime-local, date-local, time-local: string (source text); -// compared after normalizing the date/time separator to "T", uppercasing -// "Z", padding omitted seconds to ":00", and trimming trailing zeros from -// fractional seconds +// - datetime: Temporal.Instant; datetime-local: Temporal.PlainDateTime; +// date-local: Temporal.PlainDate; time-local: Temporal.PlainTime, +// compared with plain toEqual (deepEquals compares Temporal objects by +// class and value) // - invalid documents throw SyntaxError; the exact full message is asserted // where the in-tree parser produced a SyntaxError at generation time // @@ -265,78 +265,33 @@ let output = `// Tests generated from the official toml-lang/toml-test conforman import { TOML } from "bun"; import { describe, expect, test } from "bun:test"; -class TomlDateTime { - constructor( - public kind: ${kindUnion}, - public value: string, - ) {} -} -function dt(kind: TomlDateTime["kind"], value: string): TomlDateTime { - return new TomlDateTime(kind, value); -} - -function normalizeDateTime(s: string): string { - return s - .replace(/^(\\d{4}-\\d{2}-\\d{2})[ tT]/, "$1T") - .replace(/[zZ]$/, "Z") - .replace(/(^|T)(\\d{2}:\\d{2})(?=[Z+-]|$)/, "$1$2:00") - .replace(/\\.(\\d+)/, (_, frac: string) => { - const trimmed = frac.replace(/0+$/, ""); - return trimmed === "" ? "" : "." + trimmed; - }); -} - -// Datetime markers become normalized strings; everything else is unchanged. -function normalizeExpected(expected: unknown): unknown { - if (expected instanceof TomlDateTime) return normalizeDateTime(expected.value); - if (Array.isArray(expected)) return expected.map(normalizeExpected); - if (expected !== null && typeof expected === "object") { - const out: Record = Object.create(null); - for (const [k, v] of Object.entries(expected)) out[k] = normalizeExpected(v); - return out; - } - return expected; -} - -// Normalize the positions of \`actual\` that \`expected\` marks as datetimes, in -// lockstep, so a single toEqual compares everything else exactly. -function normalizeActual(actual: unknown, expected: unknown): unknown { - if (expected instanceof TomlDateTime) { - return typeof actual === "string" ? normalizeDateTime(actual) : actual; - } - if (Array.isArray(expected) && Array.isArray(actual)) { - return actual.map((a, i) => normalizeActual(a, expected[i])); - } - if ( - expected !== null && - typeof expected === "object" && - actual !== null && - typeof actual === "object" && - !Array.isArray(actual) - ) { - const out: Record = Object.create(null); - for (const [k, v] of Object.entries(actual)) out[k] = normalizeActual(v, (expected as any)[k]); - return out; - } - return actual; -} +const TEMPORAL_CLASS = { + "datetime": "Instant", + "datetime-local": "PlainDateTime", + "date-local": "PlainDate", + "time-local": "PlainTime", +} as const; -function expectTomlEqual(parsed: unknown, expected: unknown): void { - expect(normalizeActual(parsed, expected)).toEqual(normalizeExpected(expected) as any); +// The corpus value is TOML source text; TOML.parse truncates fractional +// seconds to Temporal's 9-digit limit (truncated, not rounded), and +// Temporal.*.from accepts the rest of TOML's spellings (space separator, +// lowercase t/z, omitted seconds) as is. +function dt(kind: keyof typeof TEMPORAL_CLASS, value: string) { + return (Temporal as any)[TEMPORAL_CLASS[kind]].from(value.replace(/\\.(\\d{9})\\d+/, ".$1")); } `; output += `\n// Each case also asserts that parse(stringify(parse(input))) produces the same // value: stringify must never emit a document its own parse rejects or reads -// back differently. The TOML text may change (date/times come back as quoted -// strings), but the JS value is a fixed point after one lap. +// back differently. The TOML text may change (layout, normalized date/time +// spellings), but the JS value is a fixed point after one lap. describe("toml-test/valid", () => {\n`; for (const tc of validCases) { output += ` test(${jsString(tc.name)}, () => {\n`; output += ` const input: string = ${jsString(tc.input)};\n`; output += ` const expected: any = ${valueToJS(tc.expected, 2)};\n`; - output += ` expectTomlEqual(TOML.parse(input), expected);\n`; - output += ` expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected);\n`; + output += ` expect(TOML.parse(input)).toEqual(expected);\n`; + output += ` expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected);\n`; output += ` });\n\n`; } output += `});\n`; diff --git a/test/js/bun/toml/toml-test-suite.test.ts b/test/js/bun/toml/toml-test-suite.test.ts index 30d9ae025da6..8d72d569eed1 100644 --- a/test/js/bun/toml/toml-test-suite.test.ts +++ b/test/js/bun/toml/toml-test-suite.test.ts @@ -6,10 +6,10 @@ // TOML type encoding asserted by these tests: // - integer: number; values outside Number.MAX_SAFE_INTEGER throw (TOML // requires lossless handling or an error — see the out-of-range block) -// - datetime, datetime-local, date-local, time-local: string (source text); -// compared after normalizing the date/time separator to "T", uppercasing -// "Z", padding omitted seconds to ":00", and trimming trailing zeros from -// fractional seconds +// - datetime: Temporal.Instant; datetime-local: Temporal.PlainDateTime; +// date-local: Temporal.PlainDate; time-local: Temporal.PlainTime, +// compared with plain toEqual (deepEquals compares Temporal objects by +// class and value) // - invalid documents throw SyntaxError; the exact full message is asserted // where the in-tree parser produced a SyntaxError at generation time // @@ -18,76 +18,31 @@ import { TOML } from "bun"; import { describe, expect, test } from "bun:test"; -class TomlDateTime { - constructor( - public kind: "datetime" | "datetime-local" | "date-local" | "time-local", - public value: string, - ) {} -} -function dt(kind: TomlDateTime["kind"], value: string): TomlDateTime { - return new TomlDateTime(kind, value); -} - -function normalizeDateTime(s: string): string { - return s - .replace(/^(\d{4}-\d{2}-\d{2})[ tT]/, "$1T") - .replace(/[zZ]$/, "Z") - .replace(/(^|T)(\d{2}:\d{2})(?=[Z+-]|$)/, "$1$2:00") - .replace(/\.(\d+)/, (_, frac: string) => { - const trimmed = frac.replace(/0+$/, ""); - return trimmed === "" ? "" : "." + trimmed; - }); -} - -// Datetime markers become normalized strings; everything else is unchanged. -function normalizeExpected(expected: unknown): unknown { - if (expected instanceof TomlDateTime) return normalizeDateTime(expected.value); - if (Array.isArray(expected)) return expected.map(normalizeExpected); - if (expected !== null && typeof expected === "object") { - const out: Record = Object.create(null); - for (const [k, v] of Object.entries(expected)) out[k] = normalizeExpected(v); - return out; - } - return expected; -} - -// Normalize the positions of `actual` that `expected` marks as datetimes, in -// lockstep, so a single toEqual compares everything else exactly. -function normalizeActual(actual: unknown, expected: unknown): unknown { - if (expected instanceof TomlDateTime) { - return typeof actual === "string" ? normalizeDateTime(actual) : actual; - } - if (Array.isArray(expected) && Array.isArray(actual)) { - return actual.map((a, i) => normalizeActual(a, expected[i])); - } - if ( - expected !== null && - typeof expected === "object" && - actual !== null && - typeof actual === "object" && - !Array.isArray(actual) - ) { - const out: Record = Object.create(null); - for (const [k, v] of Object.entries(actual)) out[k] = normalizeActual(v, (expected as any)[k]); - return out; - } - return actual; -} - -function expectTomlEqual(parsed: unknown, expected: unknown): void { - expect(normalizeActual(parsed, expected)).toEqual(normalizeExpected(expected) as any); +const TEMPORAL_CLASS = { + "datetime": "Instant", + "datetime-local": "PlainDateTime", + "date-local": "PlainDate", + "time-local": "PlainTime", +} as const; + +// The corpus value is TOML source text; TOML.parse truncates fractional +// seconds to Temporal's 9-digit limit (truncated, not rounded), and +// Temporal.*.from accepts the rest of TOML's spellings (space separator, +// lowercase t/z, omitted seconds) as is. +function dt(kind: keyof typeof TEMPORAL_CLASS, value: string) { + return (Temporal as any)[TEMPORAL_CLASS[kind]].from(value.replace(/\.(\d{9})\d+/, ".$1")); } // Each case also asserts that parse(stringify(parse(input))) produces the same // value: stringify must never emit a document its own parse rejects or reads -// back differently. The TOML text may change (date/times come back as quoted -// strings), but the JS value is a fixed point after one lap. +// back differently. The TOML text may change (layout, normalized date/time +// spellings), but the JS value is a fixed point after one lap. describe("toml-test/valid", () => { test("valid/array/array-subtables", () => { const input: string = "[[arr]]\n[arr.subtab]\nval=1\n\n[[arr]]\n[arr.subtab]\nval=2\n"; const expected: any = { arr: [{ subtab: { val: 1 } }, { subtab: { val: 2 } }] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/array", () => { @@ -105,22 +60,22 @@ describe("toml-test/valid", () => { ints: [1, 2, 3], strings: ["a", "b", "c"], }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/bool", () => { const input: string = "a = [true, false]\n"; const expected: any = { a: [true, false] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/empty", () => { const input: string = "thevoid = [[[[[]]]]]\n"; const expected: any = { thevoid: [[[[[]]]]] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/hetergeneous", () => { @@ -132,29 +87,29 @@ describe("toml-test/valid", () => { [1.1, 2.1], ], }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/mixed-int-array", () => { const input: string = 'arrays-and-ints = [1, ["Arrays are not integers."]]\n'; const expected: any = { "arrays-and-ints": [1, ["Arrays are not integers."]] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/mixed-int-float", () => { const input: string = "ints-and-floats = [1, 1.1]\n"; const expected: any = { "ints-and-floats": [1, 1.1] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/mixed-int-string", () => { const input: string = 'strings-and-ints = ["hi", 42]\n'; const expected: any = { "strings-and-ints": ["hi", 42] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/mixed-string-table", () => { @@ -171,120 +126,120 @@ describe("toml-test/valid", () => { ], mixed: [{ k: "a" }, "b", 1], }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/nested-double", () => { const input: string = 'nest = [\n\t[\n\t\t["a"],\n\t\t[1, 2, [3]]\n\t]\n]\n'; const expected: any = { nest: [[["a"], [1, 2, [3]]]] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/nested-inline-table", () => { const input: string = "a = [ { b = {} } ]\n"; const expected: any = { a: [{ b: {} }] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/nested", () => { const input: string = 'nest = [["a"], ["b"]]\n'; const expected: any = { nest: [["a"], ["b"]] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/nospaces", () => { const input: string = "ints = [1,2,3]\n"; const expected: any = { ints: [1, 2, 3] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/open-parent-table", () => { const input: string = "[[parent-table.arr]]\n[[parent-table.arr]]\n[parent-table]\nnot-arr = 1\n"; const expected: any = { "parent-table": { "not-arr": 1, arr: [{}, {}] } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/string-quote-comma-01", () => { const input: string = 'title = [\n"Client: \\"XXXX\\", Job: XXXX",\n"Code: XXXX"\n]\n'; const expected: any = { title: ['Client: "XXXX", Job: XXXX', "Code: XXXX"] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/string-quote-comma-02", () => { const input: string = 'title = [ " \\", ",]\n'; const expected: any = { title: [' ", '] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/string-with-comma-01", () => { const input: string = 'title = [\n"Client: XXXX, Job: XXXX",\n"Code: XXXX"\n]\n'; const expected: any = { title: ["Client: XXXX, Job: XXXX", "Code: XXXX"] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/string-with-comma-02", () => { const input: string = 'title = [\n"""Client: XXXX,\nJob: XXXX""",\n"Code: XXXX"\n]\n'; const expected: any = { title: ["Client: XXXX,\nJob: XXXX", "Code: XXXX"] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/strings", () => { const input: string = "string_array = [ \"all\", 'strings', \"\"\"are the same\"\"\", '''type''']\n"; const expected: any = { string_array: ["all", "strings", "are the same", "type"] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/table-array-string-backslash", () => { const input: string = 'foo = [ { bar="\\"{{baz}}\\""} ]\n'; const expected: any = { foo: [{ bar: '"{{baz}}"' }] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/array/trailing-comma", () => { const input: string = "arr-1 = [1,]\n\narr-2 = [2,3,]\n\narr-3 = [4,\n]\n\narr-4 = [\n\t5,\n\t6,\n]\n"; const expected: any = { "arr-1": [1], "arr-3": [4], "arr-2": [2, 3], "arr-4": [5, 6] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/bool/bool", () => { const input: string = "t = true\nf = false\n"; const expected: any = { f: false, t: true }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/comment/after-literal-no-ws", () => { const input: string = "inf=inf#infinity\nnan=nan#not a number\ntrue=true#true\nfalse=false#false\n"; const expected: any = { false: false, inf: Infinity, nan: NaN, true: true }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/comment/at-eof", () => { const input: string = '# This is a full-line comment\nkey = "value" # This is a comment at the end of a line\n'; const expected: any = { key: "value" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/comment/at-eof2", () => { const input: string = '# This is a full-line comment\nkey = "value" # This is a comment at the end of a line\n'; const expected: any = { key: "value" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/comment/everywhere", () => { @@ -299,22 +254,22 @@ describe("toml-test/valid", () => { more: [42, 42], }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/comment/noeol", () => { const input: string = "# single comment without any eol characters"; const expected: any = {}; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/comment/nonascii", () => { const input: string = "# ~ \u0080 ÿ ퟿  ￿ 𐀀 􏿿\n"; const expected: any = {}; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/comment/tricky", () => { @@ -340,8 +295,8 @@ describe("toml-test/valid", () => { two: "22#", }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/datetime/datetime", () => { @@ -351,8 +306,8 @@ describe("toml-test/valid", () => { lower: dt("datetime", "1987-07-05T17:45:00Z"), space: dt("datetime", "1987-07-05T17:45:00Z"), }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/datetime/edge", () => { @@ -366,15 +321,15 @@ describe("toml-test/valid", () => { "last-local": dt("datetime-local", "9999-12-31T23:59:59"), "last-offset": dt("datetime", "9999-12-31T23:59:59Z"), }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/datetime/invalid-date-in-string", () => { const input: string = "s = '2020-01-01x'\n"; const expected: any = { s: "2020-01-01x" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/datetime/leap-year", () => { @@ -388,15 +343,15 @@ describe("toml-test/valid", () => { "2024-datetime": dt("datetime", "2024-02-29T15:15:15Z"), "2024-datetime-local": dt("datetime-local", "2024-02-29T15:15:15"), }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/datetime/local-date", () => { const input: string = "bestdayever = 1987-07-05\n"; const expected: any = { bestdayever: dt("date-local", "1987-07-05") }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/datetime/local-time", () => { @@ -405,8 +360,8 @@ describe("toml-test/valid", () => { besttimeever: dt("time-local", "17:45:00"), milliseconds: dt("time-local", "10:32:00.555"), }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/datetime/local", () => { @@ -416,8 +371,8 @@ describe("toml-test/valid", () => { milli: dt("datetime-local", "1977-12-21T10:32:00.555"), space: dt("datetime-local", "1987-07-05T17:45:00"), }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/datetime/milliseconds", () => { @@ -429,8 +384,8 @@ describe("toml-test/valid", () => { wita1: dt("datetime", "1987-07-05T17:45:56.123+08:00"), wita2: dt("datetime", "1987-07-05T17:45:56.600+08:00"), }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/datetime/no-seconds", () => { @@ -442,8 +397,8 @@ describe("toml-test/valid", () => { "without-seconds-3": dt("datetime", "1979-05-27T07:32:00-07:00"), "without-seconds-4": dt("datetime-local", "1979-05-27T07:32:00"), }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/datetime/timezone", () => { @@ -455,43 +410,43 @@ describe("toml-test/valid", () => { pdt: dt("datetime", "1987-07-05T17:45:56-05:00"), utc: dt("datetime", "1987-07-05T17:45:56Z"), }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/empty-crlf", () => { const input: string = "\r\n"; const expected: any = {}; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/empty-lf", () => { const input: string = "\n"; const expected: any = {}; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/empty-nothing", () => { const input: string = ""; const expected: any = {}; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/empty-space", () => { const input: string = " "; const expected: any = {}; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/empty-tab", () => { const input: string = "\t"; const expected: any = {}; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/example", () => { @@ -501,8 +456,8 @@ describe("toml-test/valid", () => { "best-day-ever": dt("datetime", "1987-07-05T17:45:00Z"), numtheory: { boring: false, perfection: [6, 28, 496] }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/float/exponent-upper", () => { @@ -518,8 +473,8 @@ describe("toml-test/valid", () => { "zero-exp": 3, "zero-plus": 0, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/float/exponent", () => { @@ -535,8 +490,8 @@ describe("toml-test/valid", () => { "zero-exp": 3, "zero-plus": 0, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/float/float", () => { @@ -549,8 +504,8 @@ describe("toml-test/valid", () => { "zero-intpart": 0.123, "leading-zero-fractional": 0.0123, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/float/inf-and-nan", () => { @@ -564,30 +519,30 @@ describe("toml-test/valid", () => { nan_neg: NaN, nan_plus: NaN, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/float/long", () => { const input: string = "longpi = 3.141592653589793\nneglongpi = -3.141592653589793\n"; const expected: any = { longpi: 3.141592653589793, neglongpi: -3.141592653589793 }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/float/max-int", () => { const input: string = "# Maximum and minimum safe natural numbers.\nmax_float = 9_007_199_254_740_991.0\nmin_float = -9_007_199_254_740_991.0\n"; const expected: any = { max_float: 9007199254740991, min_float: -9007199254740991 }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/float/underscore", () => { const input: string = "before = 3_141.5927\nafter = 3141.592_7\nexponent = 3e1_4\n"; const expected: any = { after: 3141.5927, before: 3141.5927, exponent: 300000000000000 }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/float/zero", () => { @@ -602,29 +557,29 @@ describe("toml-test/valid", () => { "signed-pos": 0, zero: 0, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/implicit-and-explicit-after", () => { const input: string = "[a.b.c]\nanswer = 42\n\n[a]\nbetter = 43\n"; const expected: any = { a: { better: 43, b: { c: { answer: 42 } } } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/implicit-and-explicit-before", () => { const input: string = "[a]\nbetter = 43\n\n[a.b.c]\nanswer = 42\n"; const expected: any = { a: { better: 43, b: { c: { answer: 42 } } } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/implicit-groups", () => { const input: string = "[a.b.c]\nanswer = 42\n"; const expected: any = { a: { b: { c: { answer: 42 } } } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/array-01", () => { @@ -638,30 +593,30 @@ describe("toml-test/valid", () => { { first_name: "Bob", last_name: "Seger" }, ], }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/array-02", () => { const input: string = '# "No newlines are allowed between the curly braces unless they are valid within\n# a value"\n\na = { a = [\n]}\n'; const expected: any = { a: { a: [] } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/array-03", () => { const input: string = "b = { a = [\n\t\t1,\n\t\t2,\n\t], b = [\n\t\t3,\n\t\t4,\n\t]}\n"; const expected: any = { b: { a: [1, 2], b: [3, 4] } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/bool", () => { const input: string = "a = {a = true, b = false}\n"; const expected: any = { a: { a: true, b: false } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/empty", () => { @@ -676,15 +631,15 @@ describe("toml-test/valid", () => { many_empty: [{}, {}, {}], nested_empty: { empty: {} }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/end-in-bool", () => { const input: string = 'black = { python=">3.6", version=">=18.9b0", allow_prereleases=true }\n'; const expected: any = { black: { allow_prereleases: true, python: ">3.6", version: ">=18.9b0" } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/inline-table", () => { @@ -697,8 +652,8 @@ describe("toml-test/valid", () => { "str-key": { a: 1 }, "table-array": [{ a: 1 }, { b: 2 }], }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/key-dotted-01", () => { @@ -711,8 +666,8 @@ describe("toml-test/valid", () => { d: { a: { b: 1 } }, e: { a: { b: 1 } }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/key-dotted-02", () => { @@ -720,8 +675,8 @@ describe("toml-test/valid", () => { const expected: any = { many: { dots: { here: { dot: { dot: { dot: { a: { b: { c: 1, d: 2 } } } } } } } }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/key-dotted-03", () => { @@ -729,8 +684,8 @@ describe("toml-test/valid", () => { const expected: any = { tbl: { a: { b: { c: { d: { e: 1 } } } }, x: { a: { b: { c: { d: { e: 1 } } } } } }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/key-dotted-04", () => { @@ -741,8 +696,8 @@ describe("toml-test/valid", () => { { T: { a: { b: 2 } }, t: { a: { b: 2 } } }, ], }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/key-dotted-05", () => { @@ -754,8 +709,8 @@ describe("toml-test/valid", () => { "arr-3": [{ a: { b: 1 } }, { a: { b: 2 } }], "arr-4": ["str", { a: { b: 1 } }, { a: { b: 2 } }], }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/key-dotted-06", () => { @@ -763,23 +718,23 @@ describe("toml-test/valid", () => { const expected: any = { top: { dot: { dot: [{ dot: { dot: { dot: 1 } } }, { dot: { dot: { dot: 2 } } }] } }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/key-dotted-07", () => { const input: string = "arr = [\n\t{a.b = [{c.d = 1}]}\n]\n"; const expected: any = { arr: [{ a: { b: [{ c: { d: 1 } }] } }] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/multiline", () => { const input: string = 'tbl_multiline = { a = 1, b = """\nmultiline\n""", c = """and yet\nanother line""", d = 4 }\n'; const expected: any = { tbl_multiline: { a: 1, b: "multiline\n", c: "and yet\nanother line", d: 4 } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/nest", () => { @@ -794,8 +749,8 @@ describe("toml-test/valid", () => { tbl_tbl_empty: { tbl_0: {} }, tbl_tbl_val: { tbl_1: { one: 1 } }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/newline-comment", () => { @@ -807,8 +762,8 @@ describe("toml-test/valid", () => { "trailing-comma-1": { c: 1 }, "trailing-comma-2": { c: 1 }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/newline", () => { @@ -822,8 +777,8 @@ describe("toml-test/valid", () => { "trailing-comma-1": { c: 1 }, "trailing-comma-2": { c: 1 }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/inline-table/spaces", () => { @@ -833,23 +788,23 @@ describe("toml-test/valid", () => { "clap-1": { version: "4", features: ["derive", "cargo"] }, "clap-2": { version: "4", features: ["derive", "cargo"], nest: { a: "x", b: [1.5, 9] } }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/integer/float64-max", () => { const input: string = "# Maximum and minimum safe float64 natural numbers. Mainly here for\n# -int-as-float.\nmax_int = 9_007_199_254_740_991\nmin_int = -9_007_199_254_740_991\n"; const expected: any = { max_int: 9007199254740991, min_int: -9007199254740991 }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/integer/integer", () => { const input: string = "answer = 42\nposanswer = +42\nneganswer = -42\nzero = 0\n"; const expected: any = { answer: 42, neganswer: -42, posanswer: 42, zero: 0 }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/integer/literals", () => { @@ -866,15 +821,15 @@ describe("toml-test/valid", () => { oct2: 493, oct3: 501, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/integer/underscore", () => { const input: string = "kilo = 1_000\nx = 1_1_1_1\n"; const expected: any = { kilo: 1000, x: 1111 }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/integer/zero", () => { @@ -894,8 +849,8 @@ describe("toml-test/valid", () => { h3: 0, o1: 0, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/alphanum", () => { @@ -913,8 +868,8 @@ describe("toml-test/valid", () => { "2018_10": { "001": 1 }, "a-a-a": { _: false }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/case-sensitive", () => { @@ -930,8 +885,8 @@ describe("toml-test/valid", () => { }, section: { NAME: "upper", Name: "capitalized", name: "lower" }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/dotted-01", () => { @@ -940,8 +895,8 @@ describe("toml-test/valid", () => { many: { dots: { dot: { dot: { dot: 42 } } } }, name: { first: "Arthur", last: "Dent" }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/dotted-02", () => { @@ -950,8 +905,8 @@ describe("toml-test/valid", () => { const expected: any = { count: { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10, k: 11, l: 12 }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/dotted-03", () => { @@ -962,8 +917,8 @@ describe("toml-test/valid", () => { tbl: { a: { b: { c: 42.666 } } }, top: { key: 1 }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/dotted-04", () => { @@ -972,8 +927,8 @@ describe("toml-test/valid", () => { arr: [{ a: { b: { c: 1, d: 2 } } }, { a: { b: { c: 3, d: 4 } } }], top: { key: 1 }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/dotted-empty", () => { @@ -983,36 +938,36 @@ describe("toml-test/valid", () => { a: { "": { "": "empty.empty" } }, x: { "": "x.empty" }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/empty-01", () => { const input: string = '"" = "blank"\n'; const expected: any = { "": "blank" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/empty-02", () => { const input: string = "'' = \"blank\"\n"; const expected: any = { "": "blank" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/empty-03", () => { const input: string = "''=0\n"; const expected: any = { "": 0 }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/equals-nospace", () => { const input: string = "answer=42\n"; const expected: any = { answer: 42 }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/escapes", () => { @@ -1027,8 +982,8 @@ describe("toml-test/valid", () => { '"quoted"': { quote: true }, "a.b": { "À": {} }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/like-date", () => { @@ -1046,64 +1001,64 @@ describe("toml-test/valid", () => { "2002-01-02": { k: 10, "2024-01-03": { k: 11 } }, a: { "2001-02-08": 7, "2001-02-09": { "2001-02-10": 8 } }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/numeric-01", () => { const input: string = "1 = true\n"; const expected: any = { "1": true }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/numeric-02", () => { const input: string = "1.2 = true\n"; const expected: any = { "1": { "2": true } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/numeric-03", () => { const input: string = "0123 = true\n"; const expected: any = { "0123": true }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/numeric-04", () => { const input: string = "01.23 = true\n"; const expected: any = { "01": { "23": true } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/numeric-05", () => { const input: string = "23.01 = true\n"; const expected: any = { "23": { "01": true } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/numeric-06", () => { const input: string = "-1 = true\n"; const expected: any = { "-1": true }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/numeric-07", () => { const input: string = "-01 = true\n"; const expected: any = { "-01": true }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/numeric-08", () => { const input: string = "1 = 'one'\n01 = 'zero one'\n"; const expected: any = { "1": "one", "01": "zero one" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/quoted-dots", () => { @@ -1115,8 +1070,8 @@ describe("toml-test/valid", () => { plain_table: { plain: 3, "with.dot": 4 }, table: { withdot: { "escaped.dot": 7, "key.with.dots": 6, plain: 5 } }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/quoted-unicode", () => { @@ -1129,8 +1084,8 @@ describe("toml-test/valid", () => { "l ~ \u0080 ÿ ퟿  ￿ 𐀀 􏿿": "literal key", "~ \u0080 ÿ ퟿  ￿ 𐀀 􏿿": "basic key", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/space", () => { @@ -1142,22 +1097,22 @@ describe("toml-test/valid", () => { "a b": 1, " tbl ": { "\ttab\ttab\t": "tab" }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/special-chars", () => { const input: string = '"=~!@$^&*()_+-`1234567890[]|/?><.,;:\'=" = 1\n'; const expected: any = { "=~!@$^&*()_+-`1234567890[]|/?><.,;:'=": 1 }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/special-word", () => { const input: string = 'false = false\ntrue = 1\ninf = 100000000\nnan = "ceci n\'est pas un nombre"\n\n'; const expected: any = { false: false, inf: 100000000, nan: "ceci n'est pas un nombre", true: 1 }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/start", () => { @@ -1175,15 +1130,15 @@ describe("toml-test/valid", () => { _key: { _key: 2 }, inline: { "---": { "111": 12, "---": 10, ___: 11 } }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/key/zero", () => { const input: string = "0=0\n"; const expected: any = { "0": 0 }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/multibyte", () => { @@ -1204,37 +1159,37 @@ describe("toml-test/valid", () => { }, }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/newline-crlf", () => { const input: string = 'os = "DOS"\r\nnewline = "crlf"\r\n'; const expected: any = { newline: "crlf", os: "DOS" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/newline-lf", () => { const input: string = 'os = "unix"\nnewline = "lf"\n'; const expected: any = { newline: "lf", os: "unix" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-0", () => { const input: string = '# This is a full-line comment\nkey = "value" # This is a comment at the end of a line\nanother = "# This is not a comment"\n'; const expected: any = { another: "# This is not a comment", key: "value" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-1", () => { const input: string = 'key = "value"\n'; const expected: any = { key: "value" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-10", () => { @@ -1244,29 +1199,29 @@ describe("toml-test/valid", () => { apple: { color: "red", skin: "thin", type: "fruit" }, orange: { color: "orange", skin: "thick", type: "fruit" }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-11", () => { const input: string = '3.14159 = "pi"\n'; const expected: any = { "3": { "14159": "pi" } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-12", () => { const input: string = 'str = "I\'m a string. \\"You can quote me\\". Name\\tJos\\xE9\\nLocation\\tSF."\n'; const expected: any = { str: 'I\'m a string. "You can quote me". Name\tJosé\nLocation\tSF.' }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-13", () => { const input: string = 'str1 = """\nRoses are red\nViolets are blue"""\n'; const expected: any = { str1: "Roses are red\nViolets are blue" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-14", () => { @@ -1276,8 +1231,8 @@ describe("toml-test/valid", () => { str2: "Roses are red\nViolets are blue", str3: "Roses are red\r\nViolets are blue", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-15", () => { @@ -1288,8 +1243,8 @@ describe("toml-test/valid", () => { str2: "The quick brown fox jumps over the lazy dog.", str3: "The quick brown fox jumps over the lazy dog.", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-16", () => { @@ -1301,8 +1256,8 @@ describe("toml-test/valid", () => { str6: 'Here are fifteen quotation marks: """"""""""""""".', str7: '"This," she said, "is just a pointless statement."', }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-17", () => { @@ -1314,8 +1269,8 @@ describe("toml-test/valid", () => { winpath: "C:\\Users\\nodejs\\templates", winpath2: "\\\\ServerX\\admin$\\system32\\", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-18", () => { @@ -1325,8 +1280,8 @@ describe("toml-test/valid", () => { lines: "The first newline is\ntrimmed in literal strings.\n All other whitespace\n is preserved.\n", regex2: "I [dw]on't need \\d{2} apples", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-19", () => { @@ -1337,23 +1292,23 @@ describe("toml-test/valid", () => { quot15: 'Here are fifteen quotation marks: """""""""""""""', str: "'That,' she said, 'is still pointless.'", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-20", () => { const input: string = "int1 = +99\nint2 = 42\nint3 = 0\nint4 = -17\n"; const expected: any = { int1: 99, int2: 42, int3: 0, int4: -17 }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-21", () => { const input: string = "int5 = 1_000\nint6 = 5_349_221\nint7 = 53_49_221 # Indian number system grouping\nint8 = 1_2_3_4_5 # VALID but discouraged\n"; const expected: any = { int5: 1000, int6: 5349221, int7: 5349221, int8: 12345 }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-22", () => { @@ -1367,8 +1322,8 @@ describe("toml-test/valid", () => { oct1: 342391, oct2: 493, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-23", () => { @@ -1383,30 +1338,30 @@ describe("toml-test/valid", () => { flt6: -0.02, flt7: 6.626e-34, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-24", () => { const input: string = "flt8 = 224_617.445_991_228\n"; const expected: any = { flt8: 224617.445991228 }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-25", () => { const input: string = "# infinity\nsf1 = inf # positive infinity\nsf2 = +inf # positive infinity\nsf3 = -inf # negative infinity\n\n# not a number\nsf4 = nan # actual sNaN/qNaN encoding is implementation-specific\nsf5 = +nan # same as `nan`\nsf6 = -nan # valid, actual encoding is implementation-specific\n"; const expected: any = { sf1: Infinity, sf2: Infinity, sf3: -Infinity, sf4: NaN, sf5: NaN, sf6: NaN }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-26", () => { const input: string = "bool1 = true\nbool2 = false\n"; const expected: any = { bool1: true, bool2: false }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-27", () => { @@ -1418,15 +1373,15 @@ describe("toml-test/valid", () => { odt3: dt("datetime", "1979-05-27T00:32:00.5-07:00"), odt4: dt("datetime", "1979-05-27T00:32:00.999-07:00"), }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-28", () => { const input: string = "odt4 = 1979-05-27 07:32:00Z\n"; const expected: any = { odt4: dt("datetime", "1979-05-27T07:32:00Z") }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-29", () => { @@ -1435,15 +1390,15 @@ describe("toml-test/valid", () => { odt5: dt("datetime", "1979-05-27T07:32:00Z"), odt6: dt("datetime", "1979-05-27T07:32:00-07:00"), }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-3", () => { const input: string = 'key = "value"\nbare_key = "value"\nbare-key = "value"\n1234 = "value"\n'; const expected: any = { "1234": "value", "bare-key": "value", bare_key: "value", key: "value" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-30", () => { @@ -1453,22 +1408,22 @@ describe("toml-test/valid", () => { ldt2: dt("datetime-local", "1979-05-27T07:32:00.5"), ldt3: dt("datetime-local", "1979-05-27T00:32:00.999"), }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-31", () => { const input: string = "ldt3 = 1979-05-27T07:32\n"; const expected: any = { ldt3: dt("datetime-local", "1979-05-27T07:32:00") }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-32", () => { const input: string = "ld1 = 1979-05-27\n"; const expected: any = { ld1: dt("date-local", "1979-05-27") }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-33", () => { @@ -1478,15 +1433,15 @@ describe("toml-test/valid", () => { lt2: dt("time-local", "00:32:00.5"), lt3: dt("time-local", "00:32:00.999"), }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-34", () => { const input: string = "lt3 = 07:32\n"; const expected: any = { lt3: dt("time-local", "07:32:00") }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-35", () => { @@ -1514,22 +1469,22 @@ describe("toml-test/valid", () => { numbers: [0.1, 0.2, 0.5, 1, 2, 5], string_array: ["all", "strings", "are the same", "type"], }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-36", () => { const input: string = "integers2 = [\n 1, 2, 3\n]\n\nintegers3 = [\n 1,\n 2, # this is ok\n]\n"; const expected: any = { integers2: [1, 2, 3], integers3: [1, 2] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-37", () => { const input: string = "[table]\n"; const expected: any = { table: {} }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-38", () => { @@ -1539,15 +1494,15 @@ describe("toml-test/valid", () => { "table-1": { key1: "some string", key2: 123 }, "table-2": { key1: "another string", key2: 456 }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-39", () => { const input: string = '[dog."tater.man"]\ntype.name = "pug"\n'; const expected: any = { dog: { "tater.man": { type: { name: "pug" } } } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-4", () => { @@ -1560,8 +1515,8 @@ describe("toml-test/valid", () => { 'quoted "value"': "value", "ʎǝʞ": "value", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-40", () => { @@ -1573,30 +1528,30 @@ describe("toml-test/valid", () => { g: { h: { i: {} } }, j: { "ʞ": { l: {} } }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-41", () => { const input: string = "# [x] you\n# [x.y] don't\n# [x.y.z] need these\n[x.y.z.w] # for this to work\n\n[x] # defining a super-table afterward is ok\n"; const expected: any = { x: { y: { z: { w: {} } } } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-42", () => { const input: string = "# VALID BUT DISCOURAGED\n[fruit.apple]\n[animal]\n[fruit.orange]\n"; const expected: any = { animal: {}, fruit: { apple: {}, orange: {} } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-43", () => { const input: string = "# RECOMMENDED\n[fruit.apple]\n[fruit.orange]\n[animal]\n"; const expected: any = { animal: {}, fruit: { apple: {}, orange: {} } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-44", () => { @@ -1607,16 +1562,16 @@ describe("toml-test/valid", () => { name: "Fido", owner: { member_since: dt("date-local", "1999-08-04"), name: "Regina Dogman" }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-45", () => { const input: string = 'fruit.apple.color = "red"\n# Defines a table named fruit\n# Defines a table named fruit.apple\n\nfruit.apple.taste.sweet = true\n# Defines a table named fruit.apple.taste\n# fruit and fruit.apple were already created\n'; const expected: any = { fruit: { apple: { color: "red", taste: { sweet: true } } } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-46", () => { @@ -1625,8 +1580,8 @@ describe("toml-test/valid", () => { const expected: any = { fruit: { apple: { color: "red", taste: { sweet: true }, texture: { smooth: true } } }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-47", () => { @@ -1641,8 +1596,8 @@ describe("toml-test/valid", () => { name: { first: "Tom", last: "Preston-Werner" }, point: { x: 1, y: 2 }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-48", () => { @@ -1657,22 +1612,22 @@ describe("toml-test/valid", () => { name: { first: "Tom", last: "Preston-Werner" }, point: { x: 1, y: 2 }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-49", () => { const input: string = '[product]\ntype = { name = "Nail" }\n# type.edible = false # INVALID\n'; const expected: any = { product: { type: { name: "Nail" } } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-50", () => { const input: string = '[product]\ntype.name = "Nail"\n# type = { edible = false } # INVALID\n'; const expected: any = { product: { type: { name: "Nail" } } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-51", () => { @@ -1681,8 +1636,8 @@ describe("toml-test/valid", () => { const expected: any = { product: [{ name: "Hammer", sku: 738594937 }, {}, { color: "gray", name: "Nail", sku: 284758393 }], }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-52", () => { @@ -1698,8 +1653,8 @@ describe("toml-test/valid", () => { { name: "banana", varieties: [{ name: "plantain" }] }, ], }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-53", () => { @@ -1712,8 +1667,8 @@ describe("toml-test/valid", () => { { x: 2, y: 4, z: 8 }, ], }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-6", () => { @@ -1724,24 +1679,24 @@ describe("toml-test/valid", () => { physical: { color: "orange", shape: "round" }, site: { "google.com": true }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-7", () => { const input: string = 'fruit.name = "banana" # this is best practice\nfruit. color = "yellow" # same as fruit.color\nfruit . flavor = "banana" # same as fruit.flavor\n'; const expected: any = { fruit: { color: "yellow", flavor: "banana", name: "banana" } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-8", () => { const input: string = '# This makes the key "fruit" into a table.\nfruit.apple.smooth = true\n\n# So then you can add to the table "fruit" like so:\nfruit.orange = 2\n'; const expected: any = { fruit: { orange: 2, apple: { smooth: true } } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-1.1.0/common-9", () => { @@ -1751,8 +1706,8 @@ describe("toml-test/valid", () => { apple: { color: "red", skin: "thin", type: "fruit" }, orange: { color: "orange", skin: "thick", type: "fruit" }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-example-1-compact", () => { @@ -1779,8 +1734,8 @@ describe("toml-test/valid", () => { beta: { dc: "eqdc10", ip: "10.0.0.2" }, }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/spec-example-1", () => { @@ -1807,50 +1762,50 @@ describe("toml-test/valid", () => { beta: { dc: "eqdc10", ip: "10.0.0.2" }, }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/basic-escape-01", () => { const input: string = '# Escape "\ntest = "\\"one\\""\n'; const expected: any = { test: '"one"' }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/basic-escape-02", () => { const input: string = '# Escape \\ and then "\ntest = "\\\\\\"one"\n'; const expected: any = { test: '\\"one' }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/basic-escape-03", () => { const input: string = '# Escape \\ four times and then "\ntest = "\\\\\\\\\\\\\\\\\\"one"\n'; const expected: any = { test: '\\\\\\\\"one' }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/empty", () => { const input: string = 'answer = ""\n'; const expected: any = { answer: "" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/ends-in-whitespace-escape", () => { const input: string = 'beee = """\nheeee\ngeeee\\ \n\n\n """\n'; const expected: any = { beee: "heeee\ngeeee" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/escape-esc", () => { const input: string = 'esc = "\\e There is no escape! \\e"\n'; const expected: any = { esc: "\u001b There is no escape! \u001b" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/escape-tricky", () => { @@ -1865,15 +1820,15 @@ describe("toml-test/valid", () => { multiline_not_unicode: "\\u0041", multiline_unicode: " ", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/escaped-escape", () => { const input: string = 'answer = "\\\\x64"\n'; const expected: any = { answer: "\\x64" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/escapes", () => { @@ -1894,8 +1849,8 @@ describe("toml-test/valid", () => { tab: "|\t.", unitseparator: "|\u001f.", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/hex-escape", () => { @@ -1911,8 +1866,8 @@ describe("toml-test/valid", () => { nul: "\u0000", whitespace: " \t \u001b \r\n", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/multibyte-escape", () => { @@ -1924,8 +1879,8 @@ describe("toml-test/valid", () => { "basic-2": "ɑ € 𐫱 ɑ€𐫱", "ml-basic-2": "ɑ € 𐫱 ɑ€𐫱", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/multibyte", () => { @@ -1937,24 +1892,24 @@ describe("toml-test/valid", () => { "ml-raw": "ɑ € 𐫱 ɑ€𐫱", raw: "ɑ € 𐫱 ɑ€𐫱", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/multiline-empty", () => { const input: string = 'empty-1 = """"""\n\n# A newline immediately following the opening delimiter will be trimmed.\nempty-2 = """\n"""\n\n# \\ at the end of line trims newlines as well; note that last \\ is followed by\n# two spaces, which are ignored.\nempty-3 = """\\\n """\nempty-4 = """\\\n \\\n \\ \n """\n\n'; const expected: any = { "empty-1": "", "empty-2": "", "empty-3": "", "empty-4": "" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/multiline-escaped-crlf", () => { const input: string = '# The following line should be an unescaped backslash followed by a Windows\r\n# newline sequence ("\\r\\n")\r\n0="""\\\r\n"""\r\n'; const expected: any = { "0": "" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/multiline-quotes", () => { @@ -1975,8 +1930,8 @@ describe("toml-test/valid", () => { two: '""two quotes""', two_space: ' ""two quotes"" ', }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/multiline", () => { @@ -1993,8 +1948,8 @@ describe("toml-test/valid", () => { "no-space": "ab", "whitespace-after-bs": "The quick brown fox jumps over the lazy dog.", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/nl", () => { @@ -2007,8 +1962,8 @@ describe("toml-test/valid", () => { nl_end: "value\n", nl_mid: "val\nue", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/quoted-unicode", () => { @@ -2021,15 +1976,15 @@ describe("toml-test/valid", () => { not_escaped_string: "\\u0000 \\u0008 \\u000c \\U00000041 \\u007f \\u0080 \\u00ff \\ud7ff \\ue000 \\uffff \\U00010000 \\U0010ffff", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/raw-empty", () => { const input: string = "empty = ''\n"; const expected: any = { empty: "" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/raw-multiline", () => { @@ -2042,8 +1997,8 @@ describe("toml-test/valid", () => { oneline: "This string has a ' quote character.", "this-str-has-apostrophes": "' there's one already\n'' two more\n''", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/raw", () => { @@ -2059,23 +2014,23 @@ describe("toml-test/valid", () => { tab: "This string has a \\t tab character.", unescaped_tab: "This string has an \t unescaped tab character.", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/simple", () => { const input: string = 'answer = "You are not drinking enough whisky."\n'; const expected: any = { answer: "You are not drinking enough whisky." }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/start-mb", () => { const input: string = '# Start first line with a multibyte character.\n#\n# https://github.com/marzer/tomlplusplus/issues/190\ns1 = "§"\ns2 = \'§\'\ns3 = """\\\n§"""\ns4 = """\n§"""\ns5 = """§"""\ns6 = \'\'\'\n§\'\'\'\ns7 = \'\'\'§\'\'\'\n'; const expected: any = { s1: "§", s2: "§", s3: "§", s4: "§", s5: "§", s6: "§", s7: "§" }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/unicode-escape", () => { @@ -2097,8 +2052,8 @@ describe("toml-test/valid", () => { "null-1": "\u0000", "null-2": "\u0000", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/string/with-pound", () => { @@ -2108,36 +2063,36 @@ describe("toml-test/valid", () => { pound: "We see no # comments here.", poundcomment: "But there are # some comments here.", }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/array-empty-name", () => { const input: string = "# Silly thing to do, but valid.\n\n[['']]\na = 1\n[['']]\na = 2\n"; const expected: any = { "": [{ a: 1 }, { a: 2 }] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/array-empty", () => { const input: string = "[[a]]\n"; const expected: any = { a: [{}] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/array-implicit-and-explicit-after", () => { const input: string = "[[a.b]]\nx = 1\n\n[a]\ny = 2\n"; const expected: any = { a: { b: [{ x: 1 }], y: 2 } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/array-implicit", () => { const input: string = '[[albums.songs]]\nname = "Glory Days"\n'; const expected: any = { albums: { songs: [{ name: "Glory Days" }] } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/array-many", () => { @@ -2150,8 +2105,8 @@ describe("toml-test/valid", () => { { first_name: "Bob", last_name: "Seger" }, ], }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/array-nest", () => { @@ -2169,58 +2124,58 @@ describe("toml-test/valid", () => { }, ], }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/array-one", () => { const input: string = '[[people]]\nfirst_name = "Bruce"\nlast_name = "Springsteen"\n'; const expected: any = { people: [{ first_name: "Bruce", last_name: "Springsteen" }] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/array-table-array", () => { const input: string = '[[a]]\n [[a.b]]\n [a.b.c]\n d = "val0"\n [[a.b]]\n [a.b.c]\n d = "val1"\n'; const expected: any = { a: [{ b: [{ c: { d: "val0" } }, { c: { d: "val1" } }] }] }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/array-within-dotted", () => { const input: string = '[fruit]\napple.color = "red"\n\n[[fruit.apple.seeds]]\nsize = 2\n'; const expected: any = { fruit: { apple: { color: "red", seeds: [{ size: 2 }] } } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/empty-name", () => { const input: string = "['']\nx = 1\n\n[\"\".a]\nx = 2\n\n[a.'']\nx = 3\n"; const expected: any = { "": { x: 1, a: { x: 2 } }, a: { "": { x: 3 } } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/empty", () => { const input: string = "[a]\n"; const expected: any = { a: {} }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/keyword-with-values", () => { const input: string = "[true]\nk = 1\n\n[false]\nk = 2\n\n[inf]\nk = 3\n\n[nan]\nk = 4\n"; const expected: any = { false: { k: 2 }, inf: { k: 3 }, nan: { k: 4 }, true: { k: 1 } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/keyword", () => { const input: string = "[true]\n\n[false]\n\n[inf]\n\n[nan]\n\n\n"; const expected: any = { false: {}, inf: {}, nan: {}, true: {} }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/names-with-values", () => { @@ -2238,8 +2193,8 @@ describe("toml-test/valid", () => { j: { "ʞ": { l: { key: 7 } } }, x: { "1": { "2": { key: 8 } } }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/names", () => { @@ -2252,97 +2207,97 @@ describe("toml-test/valid", () => { j: { "ʞ": { l: {} } }, x: { "1": { "2": {} } }, }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/no-eol-01", () => { const input: string = "# No newline at end of file.\n[table]"; const expected: any = { table: {} }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/no-eol-02", () => { const input: string = "# No newline at end of file.\n[table]\na=1"; const expected: any = { table: { a: 1 } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/sub-empty", () => { const input: string = "[a]\n[a.b]\n"; const expected: any = { a: { b: {} } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/sub", () => { const input: string = '[a]\nkey = 1\n\n# a.extend is a key inside the "a" table.\n[a.extend]\nkey = 2\n\n[a.extend.more]\nkey = 3\n'; const expected: any = { a: { key: 1, extend: { key: 2, more: { key: 3 } } } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/whitespace", () => { const input: string = '["valid key"]\n'; const expected: any = { "valid key": {} }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/with-literal-string", () => { const input: string = "['a']\n[a.'\"b\"']\n[a.'\"b\"'.c]\nanswer = 42 \n"; const expected: any = { a: { '"b"': { c: { answer: 42 } } } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/with-pound", () => { const input: string = '["key#group"]\nanswer = 42\n'; const expected: any = { "key#group": { answer: 42 } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/with-single-quotes", () => { const input: string = "['a']\n[a.'b']\n[a.'b'.c]\nanswer = 42 \n"; const expected: any = { a: { b: { c: { answer: 42 } } } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/without-super-with-values", () => { const input: string = "# [x] you\n# [x.y] don't\n# [x.y.z] need these\n[x.y.z.w] # for this to work\na = 1\nb = 2\n[x] # defining a super-table afterwards is ok\nc = 3\nd = 4\n"; const expected: any = { x: { c: 3, d: 4, y: { z: { w: { a: 1, b: 2 } } } } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/table/without-super", () => { const input: string = "# [x] you\n# [x.y] don't\n# [x.y.z] need these\n[x.y.z.w] # for this to work\n[x] # defining a super-table afterwards is ok\n"; const expected: any = { x: { y: { z: { w: {} } } } }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/utf8-bom-01", () => { const input: string = "\ufeff# This file starts with an UTF-8 BOM (\\xEF\\xBB\\xBF), which isn't recommended to use but valid.\na=1\n"; const expected: any = { a: 1 }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); test("valid/utf8-bom-02", () => { const input: string = "\ufeffa=1# This file starts with an UTF-8 BOM (\\xEF\\xBB\\xBF), which isn't recommended to use but valid.\n"; const expected: any = { a: 1 }; - expectTomlEqual(TOML.parse(input), expected); - expectTomlEqual(TOML.parse(TOML.stringify(TOML.parse(input))), expected); + expect(TOML.parse(input)).toEqual(expected); + expect(TOML.parse(TOML.stringify(TOML.parse(input)))).toEqual(expected); }); }); diff --git a/test/js/bun/toml/toml.test.ts b/test/js/bun/toml/toml.test.ts index 4426fcbee1ac..80919d3c2bfa 100644 --- a/test/js/bun/toml/toml.test.ts +++ b/test/js/bun/toml/toml.test.ts @@ -1,5 +1,7 @@ import { TOML } from "bun"; import { describe, expect, test } from "bun:test"; +import { bunEnv, bunExe, tempDir } from "harness"; +import { join } from "node:path"; // Hand-written coverage beyond the official conformance suite // (toml-test-suite.test.ts): the JS-facing API surface, JS value mapping, @@ -225,23 +227,29 @@ describe("numbers", () => { }); }); -describe("date/times return their source text", () => { - test("all four kinds", () => { +describe("date/times return Temporal objects", () => { + test("all four kinds map to their Temporal type", () => { const o = TOML.parse( ["odt = 1979-05-27T07:32:00Z", "ldt = 1979-05-27T07:32:00", "ld = 1979-05-27", "lt = 07:32:00"].join("\n"), ) as any; - expect(o).toEqual({ - odt: "1979-05-27T07:32:00Z", - ldt: "1979-05-27T07:32:00", - ld: "1979-05-27", - lt: "07:32:00", - }); - for (const key of ["odt", "ldt", "ld", "lt"]) { - expect(typeof o[key]).toBe("string"); - } + expect(o.odt).toBeInstanceOf(Temporal.Instant); + expect(o.ldt).toBeInstanceOf(Temporal.PlainDateTime); + expect(o.ld).toBeInstanceOf(Temporal.PlainDate); + expect(o.lt).toBeInstanceOf(Temporal.PlainTime); + expect(o.odt.toString()).toBe("1979-05-27T07:32:00Z"); + expect(o.ldt.toString()).toBe("1979-05-27T07:32:00"); + expect(o.ld.toString()).toBe("1979-05-27"); + expect(o.lt.toString()).toBe("07:32:00"); }); - test("source spelling is preserved verbatim", () => { + test("an offset date-time specifies an instant; the written offset normalizes away", () => { + const o = TOML.parse("a = 1979-05-27T00:32:00-07:00\nb = 1979-05-27T07:32:00Z") as any; + expect(o.a.epochMilliseconds).toBe(296638320000); + expect(o.a.toString()).toBe("1979-05-27T07:32:00Z"); + expect(o.a.equals(o.b)).toBe(true); + }); + + test("TOML spellings Temporal does not print survive losslessly", () => { const o = TOML.parse( [ "lower = 1979-05-27t07:32:00.500z", @@ -249,18 +257,97 @@ describe("date/times return their source text", () => { "frac = 07:32:00.999999999", "noseconds = 07:32", "datenoseconds = 1979-05-27T07:32Z", + "leap = 1990-12-31T23:59:60Z", // RFC 3339 leap second; Temporal clamps to :59 ].join("\n"), ) as any; - expect(o.lower).toBe("1979-05-27t07:32:00.500z"); - expect(o.space).toBe("1979-05-27 07:32:00+13:00"); - expect(o.frac).toBe("07:32:00.999999999"); - expect(o.noseconds).toBe("07:32"); - expect(o.datenoseconds).toBe("1979-05-27T07:32Z"); + expect(o.lower.toString()).toBe("1979-05-27T07:32:00.5Z"); + expect(o.space.toString()).toBe("1979-05-26T18:32:00Z"); + expect(o.frac.toString()).toBe("07:32:00.999999999"); + expect(o.noseconds.toString()).toBe("07:32:00"); + expect(o.datenoseconds.toString()).toBe("1979-05-27T07:32:00Z"); + expect(o.leap.toString()).toBe("1990-12-31T23:59:59Z"); + }); + + test("fractional seconds past Temporal's 9 digits are truncated, not rounded", () => { + // TOML: "excess precision should be truncated, not rounded". + const o = TOML.parse("a = 07:32:00.123456789999\nb = 1979-05-27T07:32:00.999999999999Z") as any; + expect(o.a.toString()).toBe("07:32:00.123456789"); + expect(o.b.toString()).toBe("1979-05-27T07:32:00.999999999Z"); + }); + + test("date/times nest in arrays and inline tables", () => { + const o = TOML.parse("arr = [ 1979-05-27, 07:32:00 ]\ntbl = { d = 1979-05-27T07:32:00 }") as any; + expect(o.arr[0]).toBeInstanceOf(Temporal.PlainDate); + expect(o.arr[1]).toBeInstanceOf(Temporal.PlainTime); + expect(o.tbl.d).toBeInstanceOf(Temporal.PlainDateTime); + }); + + test("parse throws when Temporal is disabled", async () => { + // The Temporal mapping cannot exist without Temporal; a date/time value + // is then an error rather than a silently different type. + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + `try { Bun.TOML.parse("a = 1979-05-27"); console.log("no error"); } catch (e) { console.log(e.constructor.name + ": " + e.message); }` + + `console.log(JSON.stringify(Bun.TOML.parse("b = 'still works'")));`, + ], + env: { ...bunEnv, BUN_JSC_useTemporal: "0" }, + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect(stdout).toBe( + 'TypeError: Date/time values require Temporal, which is disabled in this process\n{"b":"still works"}\n', + ); + expect(stderr).toBe(""); + expect(exitCode).toBe(0); }); - test("offset date-times feed directly into Date and Temporal-style consumers", () => { - const odt = (TOML.parse("a = 1979-05-27T00:32:00-07:00") as any).a; - expect(new Date(odt).getTime()).toBe(296638320000); + test("deep dotted headers bundle without crashing", async () => { + // Dotted headers nest objects beyond safe recursion depth (the TOML + // parser builds them iteratively); the bundler's date/time lowering and + // the transform path's scan walk the tree iteratively too. A clean + // bundler error is acceptable; death by stack overflow is not. + using dir = tempDir("toml-deep-header", { + "deep.toml": "[" + Buffer.alloc(200_000, "a.").toString() + "a]\nd = 1979-05-27\n", + }); + const entry = join(String(dir), "deep.toml"); + for (const args of [ + ["build", entry], + ["build", "--no-bundle", entry], + ]) { + await using proc = Bun.spawn({ + cmd: [bunExe(), ...args], + env: bunEnv, + cwd: String(dir), + stdout: "ignore", + stderr: "pipe", + }); + // Drain stderr concurrently so the child cannot block on a full pipe. + // Exit 0 is today's behavior and a clean diagnostic exit would also be + // fine; death by signal (stack overflow) is neither. + const [exitCode] = await Promise.all([proc.exited, proc.stderr.text()]); + expect([0, 1]).toContain(exitCode); + } + }); + + test("importing a TOML module with date/times throws when Temporal is disabled", async () => { + // The import path converts the same way; the load must fail with the + // TypeError, not crash. + using dir = tempDir("toml-dates-no-temporal", { + "config.toml": "a = 1979-05-27\n", + "index.ts": `try { await import("./config.toml"); console.log("no error"); } catch (e) { console.log(e.constructor.name + ": " + e.message); }`, + }); + await using proc = Bun.spawn({ + cmd: [bunExe(), "index.ts"], + env: { ...bunEnv, BUN_JSC_useTemporal: "0" }, + cwd: String(dir), + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect(stdout).toBe("TypeError: Date/time values require Temporal, which is disabled in this process\n"); + expect(stderr).toBe(""); + expect(exitCode).toBe(0); }); }); @@ -466,13 +553,15 @@ describe("robustness", () => { test("values survive garbage collection", () => { const doc = 'a = "héllo wörld 🌍"\nb = [1, 2.5, true, "x"]\n[t]\nc = 1979-05-27\n'; - const results: unknown[] = []; + const results: any[] = []; for (let i = 0; i < 100; i++) { results.push(TOML.parse(doc)); } Bun.gc(true); for (const o of results) { - expect(o).toEqual({ a: "héllo wörld 🌍", b: [1, 2.5, true, "x"], t: { c: "1979-05-27" } }); + expect(o).toEqual({ a: "héllo wörld 🌍", b: [1, 2.5, true, "x"], t: { c: expect.anything() } }); + expect(o.t.c).toBeInstanceOf(Temporal.PlainDate); + expect(o.t.c.toString()).toBe("1979-05-27"); } }); }); @@ -630,11 +719,17 @@ describe("TOML.stringify", () => { test("Date becomes a TOML offset date-time", () => { const d = new Date(Date.UTC(1979, 4, 27, 7, 32, 0, 999)); expect(TOML.stringify({ d })).toBe("d = 1979-05-27T07:32:00.999Z\n"); - // parse returns datetimes as source-text strings. - expect(TOML.parse(TOML.stringify({ d }))).toEqual({ d: "1979-05-27T07:32:00.999Z" }); - expect(TOML.stringify({ d: new Date(0) })).toBe("d = 1970-01-01T00:00:00.000Z\n"); + // It reads back as the Temporal.Instant of the same moment. + expect(TOML.parse(TOML.stringify({ d })).d.epochMilliseconds).toBe(d.getTime()); + // Fraction uses auto precision (trailing zeros trimmed), so a Date and a + // Temporal.Instant spell the same instant identically. + expect(TOML.stringify({ d: new Date(0) })).toBe("d = 1970-01-01T00:00:00Z\n"); + expect(TOML.stringify({ d: new Date(500) })).toBe("d = 1970-01-01T00:00:00.5Z\n"); + expect(TOML.stringify({ a: new Date(0), b: Temporal.Instant.fromEpochMilliseconds(0) })).toBe( + "a = 1970-01-01T00:00:00Z\nb = 1970-01-01T00:00:00Z\n", + ); // The 4-digit-year bounds (`Date.UTC(0, ...)` remaps year 0 to 1900, so raw ms). - expect(TOML.stringify({ d: new Date(-62167219200000) })).toBe("d = 0000-01-01T00:00:00.000Z\n"); + expect(TOML.stringify({ d: new Date(-62167219200000) })).toBe("d = 0000-01-01T00:00:00Z\n"); expect(TOML.stringify({ d: new Date(Date.UTC(9999, 11, 31, 23, 59, 59, 999)) })).toBe( "d = 9999-12-31T23:59:59.999Z\n", ); @@ -651,6 +746,117 @@ describe("TOML.stringify", () => { ); }); + test("the four Temporal date/time types become unquoted TOML literals", () => { + expect( + TOML.stringify({ + odt: Temporal.Instant.from("1979-05-27T00:32:00-07:00"), + ldt: Temporal.PlainDateTime.from("1979-05-27T07:32:00"), + ld: Temporal.PlainDate.from("1979-05-27"), + lt: Temporal.PlainTime.from("07:32:00"), + }), + ).toBe( + [ + "odt = 1979-05-27T07:32:00Z", // an Instant prints in UTC + "ldt = 1979-05-27T07:32:00", + "ld = 1979-05-27", + "lt = 07:32:00", + ].join("\n") + "\n", + ); + }); + + test("Temporal values keep sub-second precision with trailing zeros trimmed", () => { + expect( + TOML.stringify({ + half: Temporal.Instant.from("1979-05-27T07:32:00.500Z"), + nanos: Temporal.PlainTime.from("07:32:00.123456789"), + }), + ).toBe("half = 1979-05-27T07:32:00.5Z\nnanos = 07:32:00.123456789\n"); + }); + + test("ZonedDateTime emits its offset and drops the time-zone annotation", () => { + const zdt = Temporal.ZonedDateTime.from("2024-06-15T12:34:56+02:00[Europe/Berlin]"); + expect(TOML.stringify({ zdt })).toBe("zdt = 2024-06-15T12:34:56+02:00\n"); + // The same instant comes back, as an Instant. + expect(TOML.parse(TOML.stringify({ zdt })).zdt.equals(zdt.toInstant())).toBe(true); + }); + + test("a ZonedDateTime with a sub-minute offset falls back to the UTC instant form", () => { + // Pre-standard-time zones use LMT: Europe/Berlin in 1800 is +00:53:28, + // which TOML's HH:MM offset grammar cannot carry. + const zdt = Temporal.ZonedDateTime.from("1800-01-01T00:00[Europe/Berlin]"); + expect(zdt.offsetNanoseconds % 60_000_000_000).not.toBe(0); + expect(TOML.stringify({ zdt })).toBe(`zdt = ${zdt.toInstant().toString()}\n`); + expect(TOML.parse(TOML.stringify({ zdt })).zdt.equals(zdt.toInstant())).toBe(true); + }); + + test("a non-ISO calendar date emits its ISO fields without the calendar annotation", () => { + const hebrew = Temporal.PlainDate.from("2024-01-01[u-ca=hebrew]"); + expect(hebrew.toString()).toBe("2024-01-01[u-ca=hebrew]"); // what TOML cannot carry + expect(TOML.stringify({ d: hebrew })).toBe("d = 2024-01-01\n"); + }); + + test("Temporal values in arrays stay inline instead of becoming [[table]] sections", () => { + expect(TOML.stringify({ a: [Temporal.PlainDate.from("1979-05-27"), Temporal.PlainTime.from("07:32:00")] })).toBe( + "a = [1979-05-27, 07:32:00]\n", + ); + }); + + test("Temporal types with no TOML representation throw", () => { + expect(stringifyError({ x: Temporal.PlainYearMonth.from("2024-01") }).message).toBe( + "TOML.stringify cannot serialize Temporal.PlainYearMonth (it has no TOML representation)", + ); + expect(stringifyError({ x: Temporal.PlainMonthDay.from("01-01") }).message).toBe( + "TOML.stringify cannot serialize Temporal.PlainMonthDay (it has no TOML representation)", + ); + expect(stringifyError({ x: Temporal.Duration.from("PT1H") }).message).toBe( + "TOML.stringify cannot serialize Temporal.Duration (it has no TOML representation)", + ); + }); + + test("Temporal values outside years 0000-9999 throw like Date does", () => { + expect(stringifyError({ d: Temporal.PlainDate.from({ year: 10000, month: 1, day: 1 }) }).message).toBe( + "TOML.stringify cannot serialize a Temporal.PlainDate outside years 0000-9999", + ); + expect(stringifyError({ d: Temporal.PlainDate.from({ year: -1, month: 12, day: 31 }) }).message).toBe( + "TOML.stringify cannot serialize a Temporal.PlainDate outside years 0000-9999", + ); + // An instant a day or more outside the range has no `±HH:MM` spelling with a 4-digit year. + expect(stringifyError({ i: Temporal.Instant.from("+010000-01-02T00:00:00Z") }).message).toBe( + "TOML.stringify cannot serialize a Temporal.Instant outside years 0000-9999", + ); + expect(stringifyError({ i: Temporal.Instant.from("-000001-12-31T00:00:00Z") }).message).toBe( + "TOML.stringify cannot serialize a Temporal.Instant outside years 0000-9999", + ); + // The boundary years themselves are fine. + expect(TOML.stringify({ d: Temporal.PlainDate.from("0000-01-01") })).toBe("d = 0000-01-01\n"); + expect(TOML.stringify({ d: Temporal.PlainDate.from("9999-12-31") })).toBe("d = 9999-12-31\n"); + }); + + test("an instant whose UTC year is outside 0000-9999 is spelled with an offset that keeps the year in range", () => { + // These are what `TOML.parse` produces for valid offset date-times at the + // year edges; the same instant must stringify, and to the same nanosecond. + const cases: [string, string][] = [ + ["0000-01-01T00:00:00+01:00", "0000-01-01T00:00:00+01:00"], + ["0000-01-01T00:20:00.5+01:00", "0000-01-01T00:20:00.5+01:00"], + ["0000-01-01T00:00:00+23:59", "0000-01-01T00:00:00+23:59"], + ["0000-01-01T00:00:30+23:59", "0000-01-01T00:00:30+23:59"], + ["9999-12-31T23:30:00-01:00", "9999-12-31T23:30:00-01:00"], + ["9999-12-31T23:59:59.999999999-23:59", "9999-12-31T23:59:59.999999999-23:59"], + // In range as UTC: `Z` is preferred over the written offset. + ["0000-01-01T01:00:00+01:00", "0000-01-01T00:00:00Z"], + ["9999-12-31T23:59:59.999999999+00:00", "9999-12-31T23:59:59.999999999Z"], + ]; + for (const [source, printed] of cases) { + const i = TOML.parse(`i = ${source}`).i as Temporal.Instant; + expect(TOML.stringify({ i })).toBe(`i = ${printed}\n`); + expect((TOML.parse(`i = ${printed}`).i as Temporal.Instant).epochNanoseconds).toBe(i.epochNanoseconds); + } + // A ZonedDateTime keeps its own offset when that spelling fits, else the same rule applies. + const zdt = Temporal.Instant.from("+010000-01-01T00:00:00Z").toZonedDateTimeISO("+02:00"); + expect(TOML.stringify({ zdt })).toBe("zdt = 9999-12-31T23:00:00-01:00\n"); + expect((TOML.parse(TOML.stringify({ zdt })).zdt as Temporal.Instant).epochNanoseconds).toBe(zdt.epochNanoseconds); + }); + test("null values throw with the offending key", () => { expect(stringifyError({ a: { broken: null } }).message).toBe( "TOML cannot represent null (key 'broken'); remove the key or use a sentinel value", @@ -679,6 +885,7 @@ describe("TOML.stringify", () => { expect(stringifyError("str").message).toBe(msg); expect(stringifyError(5).message).toBe(msg); expect(stringifyError(new Date(0)).message).toBe(msg); + expect(stringifyError(Temporal.PlainDate.from("1979-05-27")).message).toBe(msg); expect(TOML.stringify(undefined)).toBeUndefined(); }); @@ -723,30 +930,32 @@ describe("TOML.stringify", () => { // The TOML.stringify suite above covers parse(stringify(jsValue)). These cover // the other direction, stringify of a value produced by parse (read, modify, -// write back), where the four date/time types lose their TOML type. +// write back). describe("stringify(parse) round-trips", () => { - test("all four date/time types become quoted strings on the way back out", () => { - // parse returns a date/time literal as the string of its source text, so - // stringify sees a plain string and must quote it. The TOML type changes, - // but the JS value is a fixed point after one stringify/parse lap. + test("all four date/time types come back out as the same TOML type", () => { + // parse maps a date/time literal to its Temporal type, and stringify maps + // each Temporal type back to the literal it came from: the TOML type + // survives the lap (the spelling normalizes to canonical ISO form). const cases: [string, string][] = [ - ["d = 1979-05-27T07:32:00Z", 'd = "1979-05-27T07:32:00Z"\n'], - ["d = 1979-05-27T07:32:00", 'd = "1979-05-27T07:32:00"\n'], - ["d = 1979-05-27", 'd = "1979-05-27"\n'], - ["d = 07:32:00", 'd = "07:32:00"\n'], + ["d = 1979-05-27T07:32:00Z", "d = 1979-05-27T07:32:00Z\n"], + ["d = 1979-05-27T00:32:00-07:00", "d = 1979-05-27T07:32:00Z\n"], // instant; offset normalizes + ["d = 1979-05-27T07:32:00", "d = 1979-05-27T07:32:00\n"], + ["d = 1979-05-27", "d = 1979-05-27\n"], + ["d = 07:32:00", "d = 07:32:00\n"], + ["d = 07:32", "d = 07:32:00\n"], // TOML 1.1 omitted seconds ]; - for (const [doc, requoted] of cases) { - const once = TOML.parse(doc); - expect(TOML.stringify(once)).toBe(requoted); - expect(TOML.parse(TOML.stringify(once))).toEqual(once as any); + for (const [doc, out] of cases) { + const once = TOML.parse(doc) as any; + expect(TOML.stringify(once)).toBe(out); + expect((TOML.parse(TOML.stringify(once)) as any).d.toString()).toBe(once.d.toString()); } }); - test("a date literal and a string of the same text are indistinguishable after parse", () => { - // This is why the previous test cannot preserve the TOML type: both - // documents produce the identical JS value, so stringify has nothing to go on. - expect(TOML.parse("a = 1979-05-27")).toEqual({ a: "1979-05-27" }); - expect(TOML.parse('a = "1979-05-27"')).toEqual({ a: "1979-05-27" }); + test("a date literal and a string of the same text stay distinct", () => { + expect((TOML.parse("a = 1979-05-27") as any).a).toBeInstanceOf(Temporal.PlainDate); + expect((TOML.parse('a = "1979-05-27"') as any).a).toBe("1979-05-27"); + expect(TOML.stringify(TOML.parse("a = 1979-05-27"))).toBe("a = 1979-05-27\n"); + expect(TOML.stringify(TOML.parse('a = "1979-05-27"'))).toBe('a = "1979-05-27"\n'); }); test("nan, inf, -inf, and signed zero round-trip as values, not just as text", () => {