diff --git a/yaml/_dumper_state.ts b/yaml/_dumper_state.ts index 7253b4bee2b5..78088e795adc 100644 --- a/yaml/_dumper_state.ts +++ b/yaml/_dumper_state.ts @@ -39,6 +39,12 @@ const STYLE_DOUBLE = 5; const LEADING_SPACE_REGEXP = /^\n* /; +// Matches integer-shaped strings with a leading zero (e.g. "08", "09", +// "089", "-012"). resolveYamlInteger rejects these when they contain a +// non-octal digit, but they still read as numbers to humans and to other +// YAML parsers, so force-quote them for cross-parser safety. +const AMBIGUOUS_LEADING_ZERO_INT_REGEXP = /^[-+]?0[0-9]+$/; + const ESCAPE_SEQUENCES = new Map([ [0x00, "\\0"], [0x07, "\\a"], @@ -238,7 +244,8 @@ function chooseScalarStyle( if (!hasLineBreak && !hasFoldableLine) { // Strings interpretable as another type have to be quoted; // e.g. the string 'true' vs. the boolean true. - return plain && !implicitTypes.some((type) => type.resolve(string)) + return plain && !implicitTypes.some((type) => type.resolve(string)) && + !AMBIGUOUS_LEADING_ZERO_INT_REGEXP.test(string) ? STYLE_PLAIN : quoteStyle === "'" ? STYLE_SINGLE diff --git a/yaml/stringify_test.ts b/yaml/stringify_test.ts index 9db82114bb5c..844b6f4618e3 100644 --- a/yaml/stringify_test.ts +++ b/yaml/stringify_test.ts @@ -906,3 +906,24 @@ tags: ); }, }); + +Deno.test({ + name: "stringify() quotes leading-zero numeric strings", + fn() { + // resolveYamlInteger accepts "07" as octal so stringify already quoted + // it. "08" and "09" fell through because they aren't valid octal, but + // they still read as numbers to humans and to other YAML parsers, so + // they must be quoted too. See https://github.com/denoland/std/issues/7040 + assertEquals(stringify("07"), "'07'\n"); + assertEquals(stringify("08"), "'08'\n"); + assertEquals(stringify("09"), "'09'\n"); + assertEquals(stringify("089"), "'089'\n"); + assertEquals(stringify("-08"), "'-08'\n"); + assertEquals(stringify("+09"), "'+09'\n"); + // Strings that are not integer-shaped are still emitted plain. + assertEquals(stringify("0a8"), "0a8\n"); + // Already-numeric strings stay quoted as before. + assertEquals(stringify("8"), "'8'\n"); + assertEquals(stringify("0"), "'0'\n"); + }, +});