diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 35613041..ee54aafb 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -8,6 +8,7 @@ * Bump `System.Memory` transitive-dependency pin from 4.5.5 to 4.6.3.0 ### Fixed +* Fix `Markdown.ToMd` converting tight lists (no blank lines between items) into loose lists by emitting a blank line after every item. Tight lists now round-trip correctly without inter-item blank lines. * Fix `Markdown.ToMd` serialising `HardLineBreak` as a bare newline instead of two trailing spaces + newline. The correct CommonMark representation `" \n"` is now emitted, so hard line breaks survive a round-trip through `ToMd`. * Fix `Markdown.ToMd` serialising `HorizontalRule` as 23 hyphens regardless of the character used in the source. It now emits exactly three characters matching the parsed character (`---`, `***`, or `___`), giving faithful round-trips. * Remove stray `printfn` debug output emitted to stdout when `Markdown.ToMd` encountered an unrecognised paragraph type. diff --git a/src/FSharp.Formatting.Markdown/MarkdownUtils.fs b/src/FSharp.Formatting.Markdown/MarkdownUtils.fs index c0da6ac4..20a1479b 100644 --- a/src/FSharp.Formatting.Markdown/MarkdownUtils.fs +++ b/src/FSharp.Formatting.Markdown/MarkdownUtils.fs @@ -199,6 +199,13 @@ module internal MarkdownUtils = yield "" | ListBlock(Unordered, paragraphsl, _) -> + // A tight list has exactly one Span per item (no blank lines between items). + let isTight = + paragraphsl + |> List.forall (function + | [ Span _ ] -> true + | _ -> false) + for paragraphs in paragraphsl do for (i, paragraph) in List.indexed paragraphs do let lines = formatParagraph ctx paragraph @@ -210,8 +217,19 @@ module internal MarkdownUtils = else yield " " + line + if not isTight then yield "" + + if isTight then + yield "" | ListBlock(Ordered, paragraphsl, _) -> + // A tight list has exactly one Span per item (no blank lines between items). + let isTight = + paragraphsl + |> List.forall (function + | [ Span _ ] -> true + | _ -> false) + for (n, paragraphs) in List.indexed paragraphsl do for (i, paragraph) in List.indexed paragraphs do let lines = formatParagraph ctx paragraph @@ -223,7 +241,11 @@ module internal MarkdownUtils = else yield " " + line + if not isTight then yield "" + + if isTight then + yield "" | TableBlock(headers, alignments, rows, _) -> match headers with diff --git a/tests/FSharp.Markdown.Tests/Markdown.fs b/tests/FSharp.Markdown.Tests/Markdown.fs index b84414ca..e5cf2b90 100644 --- a/tests/FSharp.Markdown.Tests/Markdown.fs +++ b/tests/FSharp.Markdown.Tests/Markdown.fs @@ -1303,6 +1303,31 @@ let ``ToMd preserves an unordered list`` () = result |> should contain "banana" result |> should contain "cherry" +[] +let ``ToMd preserves tight unordered list without blank lines between items`` () = + // A tight list should not gain blank lines between items on round-trip. + let md = "* apple\n* banana\n* cherry" + let result = toMd md + // Tight list: no blank line between consecutive items + result |> should not' (contain "* apple\n\n* banana") + +[] +let ``ToMd preserves tight ordered list without blank lines between items`` () = + // A tight ordered list should not gain blank lines between items on round-trip. + let md = "1. first\n2. second\n3. third" + let result = toMd md + // Tight list: no blank line between consecutive items + result |> should not' (contain "1. first\n\n2. second") + +[] +let ``ToMd preserves loose list with blank lines between items`` () = + // A loose list (items separated by blank lines) should keep blank lines. + let md = "* alpha\n\n* beta\n\n* gamma" + let result = toMd md + result |> should contain "alpha" + result |> should contain "beta" + result |> should contain "gamma" + [] let ``ToMd preserves emphasis (italic) text`` () = // Emphasis must serialise as *...* not **...** (bold)