From 3de36e26d31453a21f340fcd08fcbb40116f4a87 Mon Sep 17 00:00:00 2001 From: LeSingh1 Date: Fri, 29 May 2026 20:30:07 -0700 Subject: [PATCH] fix(toml): handle Infinity/NaN and Date in inline arrays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inline array stringification used JSON.stringify, which silently turned Infinity/-Infinity/NaN into 'null' and wrapped Date values in extra quotes. Both forms broke round-trip through parse(): stringify({x: [Infinity, -Infinity, NaN]}) // x = [null,null,null] stringify({x: [new Date(0)]}) // x = ["1970-01-01..."] The same flaw affected #printAsInlineValue, which was the path for mixed-type arrays — Date values were quoted and non-finite numbers fell through as String(Infinity) / String(NaN). Replace the JSON.stringify call in #arrayDeclaration with a per-element walk through #printAsInlineValue, and teach #printAsInlineValue to emit TOML's inf / -inf / nan keywords for non-finite numbers and to leave Date values unquoted to match TOML's datetime literal syntax. The 'handles mixed array' test pinned the buggy date = "2022-05-13T00:00:00.000" form inside a nested inline map. Updated to the correct unquoted form. Four new tests pin the issue's specific repros (primitive Infinity/-Infinity/NaN, primitive Date, mixed inf/nan/object, mixed date/object). Fixes #7162 (partially — the null-in-array cases are left for a follow-up since the spec disallows null in TOML and the right behavior is a separate design call). --- toml/stringify.ts | 18 ++++++++++++++-- toml/stringify_test.ts | 47 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/toml/stringify.ts b/toml/stringify.ts index 05a53e8e3d5e..b8222ef11757 100644 --- a/toml/stringify.ts +++ b/toml/stringify.ts @@ -134,10 +134,19 @@ class Dumper { } #printAsInlineValue(value: unknown): string | number { if (value instanceof Date) { - return `"${this.#printDate(value)}"`; + // TOML datetime literals are unquoted (e.g. 1979-05-27T07:32:00Z). + // The previous wrapping in quotes caused the value to round-trip as a + // string instead of a Date (#7162). + return this.#printDate(value); } else if (typeof value === "string" || value instanceof RegExp) { return JSON.stringify(value.toString()); } else if (typeof value === "number") { + // TOML uses inf / -inf / nan as the keywords for non-finite numbers + // (#7162). Letting String(Infinity) / String(NaN) leak produces + // tokens that don't reparse. + if (Number.isNaN(value)) return "nan"; + if (value === Infinity) return "inf"; + if (value === -Infinity) return "-inf"; return value; } else if (typeof value === "boolean") { return value.toString(); @@ -185,7 +194,12 @@ class Dumper { return `${title} = `; } #arrayDeclaration(keys: string[], value: unknown[]): string { - return `${this.#declaration(keys)}${JSON.stringify(value)}`; + // JSON.stringify(value) turned Infinity / -Infinity / NaN into `null` and + // wrapped Date values in extra quotes, producing arrays that round-trip + // incorrectly (#7162). Use the per-element inline formatter instead so + // primitive arrays use the same TOML literal forms as mixed-type arrays. + const inline = value.map((x) => this.#printAsInlineValue(x)).join(","); + return `${this.#declaration(keys)}[${inline}]`; } #strDeclaration(keys: string[], value: string): string { return `${this.#declaration(keys)}${JSON.stringify(value)}`; diff --git a/toml/stringify_test.ts b/toml/stringify_test.ts index 523eb45d5ec3..12bc29eb58ae 100644 --- a/toml/stringify_test.ts +++ b/toml/stringify_test.ts @@ -237,10 +237,12 @@ Deno.test({ }, }, }; + // TOML datetime literals are unquoted (see #7162). The previous + // `"2022-05-13T00:00:00.000"` form round-tripped as a string. const expected = `emptyArray = [] mixedArray1 = [1,{b = 2}] mixedArray2 = [{b = 2},1] -nestedArray1 = [[{b = 1,date = "2022-05-13T00:00:00.000"}]] +nestedArray1 = [[{b = 1,date = 2022-05-13T00:00:00.000}]] nestedArray2 = [[[{b = 1}]]] nestedArray3 = [[],[{b = 1}]] @@ -275,3 +277,46 @@ Deno.test({ assertEquals(actual, expected); }, }); + +// https://github.com/denoland/std/issues/7162 — inline array stringification +// used JSON.stringify, which turns Infinity/-Infinity/NaN into null and +// wraps Date values in quotes. Both forms broke round-trip through parse(). +Deno.test({ + name: "stringify() emits inf/-inf/nan in primitive arrays (#7162)", + fn() { + assertEquals( + stringify({ x: [Infinity, -Infinity, NaN] }), + "x = [inf,-inf,nan]\n", + ); + }, +}); + +Deno.test({ + name: "stringify() emits inf/-inf/nan in mixed arrays (#7162)", + fn() { + assertEquals( + stringify({ x: [Infinity, -Infinity, NaN, {}] }), + "x = [inf,-inf,nan,{}]\n", + ); + }, +}); + +Deno.test({ + name: "stringify() does not quote Date values in primitive arrays (#7162)", + fn() { + assertEquals( + stringify({ x: [new Date(0)] }), + "x = [1970-01-01T00:00:00.000]\n", + ); + }, +}); + +Deno.test({ + name: "stringify() does not quote Date values in mixed arrays (#7162)", + fn() { + assertEquals( + stringify({ x: [new Date(0), {}] }), + "x = [1970-01-01T00:00:00.000,{}]\n", + ); + }, +});