From 762786278158a73805e28152df46f47e8acf6511 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:51:23 +0000 Subject: [PATCH 1/2] fix: Markdown.ToMd preserves tight list formatting Tight lists (no blank lines between items in source) were converted to loose lists by ToMd because the list serialiser always emitted a blank line after every item. Root cause: the Unordered and Ordered ListBlock arms in formatParagraph unconditionally yielded "" after each item, regardless of whether the list was tight or loose. Fix: detect a tight list (every item is a single Span paragraph, as produced by the parser for simple/tight items) and suppress inter-item blank lines. A single trailing blank line is still emitted after the entire list so the document structure is preserved. Added three tests: - tight unordered list round-trip (no blank lines) - tight ordered list round-trip (no blank lines) - loose list round-trip (items with blank lines preserved) All 284 Markdown tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- RELEASE_NOTES.md | 3 +++ .../MarkdownUtils.fs | 22 ++++++++++++++++ tests/FSharp.Markdown.Tests/Markdown.fs | 25 +++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 33214cbc..b38ddd59 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,9 @@ ## [Unreleased] +### 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. + ## [22.0.0] - 2026-04-03 ### Fixed diff --git a/src/FSharp.Formatting.Markdown/MarkdownUtils.fs b/src/FSharp.Formatting.Markdown/MarkdownUtils.fs index 59bf09a3..6a67950d 100644 --- a/src/FSharp.Formatting.Markdown/MarkdownUtils.fs +++ b/src/FSharp.Formatting.Markdown/MarkdownUtils.fs @@ -157,6 +157,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 @@ -168,8 +175,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 @@ -181,7 +199,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 984047f7..09d49ed8 100644 --- a/tests/FSharp.Markdown.Tests/Markdown.fs +++ b/tests/FSharp.Markdown.Tests/Markdown.fs @@ -1257,6 +1257,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) From 5d6dd9951c2dea8f0ab6a4ac051be83cb85b7b75 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 10 Apr 2026 09:51:27 +0000 Subject: [PATCH 2/2] ci: trigger checks