From 32b38e85f243281e05ce9dd4d6cb0012ecba6490 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:52:08 +0000 Subject: [PATCH 1/2] fix: Markdown.ToMd preserves indented code blocks as fenced code blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Indented code blocks (parsed with fence = None) were serialised by Markdown.ToMd as bare text without any code block markers. When the output was re-parsed, the content was interpreted as a Paragraph, not a CodeBlock — breaking the round-trip. Fix: use triple-backtick fences for code blocks that originally had no fence, ensuring the serialised form is always a valid fenced code block. Fenced code blocks round-trip correctly and render identically to indented code blocks in all Markdown renderers. Added a round-trip test to cover the case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- RELEASE_NOTES.md | 1 + .../MarkdownUtils.fs | 12 +++---- tests/FSharp.Markdown.Tests/Markdown.fs | 33 +++++++++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 72bd4ec1..55bb8073 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -7,6 +7,7 @@ * Fix crash (`failwith "tbd - IndirectImage"`) when `Markdown.ToMd` is called on a document containing reference-style images with bracket syntax. The indirect image is now serialised as `![alt](url)` when the reference is resolved, or in bracket notation when it is not. [#1094](https://github.com/fsprojects/FSharp.Formatting/pull/1094) * Fix `Markdown.ToMd` serialising italic spans with asterisks incorrectly as bold spans. [#1102](https://github.com/fsprojects/FSharp.Formatting/pull/1102) * Fix `Markdown.ToMd` serialising ordered list items with incorrect numbering and formatting. [#1102](https://github.com/fsprojects/FSharp.Formatting/pull/1102) +* Fix `Markdown.ToMd` not preserving indented code blocks: bare code output was re-parsed as a paragraph. Indented code blocks are now serialised as fenced code blocks, which round-trip correctly. ## [22.0.0-alpha.2] - 2026-03-13 diff --git a/src/FSharp.Formatting.Markdown/MarkdownUtils.fs b/src/FSharp.Formatting.Markdown/MarkdownUtils.fs index 59bf09a3..9d288155 100644 --- a/src/FSharp.Formatting.Markdown/MarkdownUtils.fs +++ b/src/FSharp.Formatting.Markdown/MarkdownUtils.fs @@ -145,15 +145,15 @@ module internal MarkdownUtils = yield "-----------------------" yield "" | CodeBlock(code = code; fence = fence; language = language) -> - match fence with - | None -> () - | Some f -> yield f + language + // Indented code blocks (fence = None) are serialised as fenced blocks so + // that the round-trip is valid — raw indented code without a '> ' prefix + // or 4-space indent would be parsed as a paragraph, not a code block. + let f = defaultArg fence "```" + yield f + language yield code - match fence with - | None -> () - | Some f -> yield f + yield f yield "" | ListBlock(Unordered, paragraphsl, _) -> diff --git a/tests/FSharp.Markdown.Tests/Markdown.fs b/tests/FSharp.Markdown.Tests/Markdown.fs index 984047f7..99ea66bf 100644 --- a/tests/FSharp.Markdown.Tests/Markdown.fs +++ b/tests/FSharp.Markdown.Tests/Markdown.fs @@ -1371,3 +1371,36 @@ let ``ToMd round-trip: indirect image with unresolved reference`` () = let result = Markdown.ToMd(doc) // When key is not resolved, should preserve the indirect form result |> should contain "![alt text][unknown-ref]" + +// -------------------------------------------------------------------------------------- +// ToMd round-trip: indented code block (fence = None) — issue #fix-tomd-indented-codeblock +// -------------------------------------------------------------------------------------- + +[] +let ``ToMd round-trip: indented code block is preserved as a code block`` () = + // An indented code block (4-space indent) is serialised as a fenced block to + // guarantee the round-trip: outputting bare code without any fence would cause + // re-parsing to produce a paragraph instead of a code block. + let input = " let x = 1\n let y = 2" + let doc = Markdown.Parse(input) + // The parser should have produced a CodeBlock, not a Paragraph + let cbs = + doc.Paragraphs + |> List.choose (function + | CodeBlock _ as cb -> Some cb + | _ -> None) + + cbs |> should haveLength 1 + // ToMd should produce a fenced form so the round-trip is valid + let result = Markdown.ToMd(doc, newline = "\n") + result |> should contain "```" + result |> should contain "let x = 1" + result |> should contain "let y = 2" + // The serialised form re-parses to a CodeBlock, not a Paragraph + let doc2 = Markdown.Parse(result) + + doc2.Paragraphs + |> List.choose (function + | CodeBlock _ as cb -> Some cb + | _ -> None) + |> should haveLength 1 From 3dedababe7f323b215d7405b2bd4a2725e468532 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 2 Apr 2026 10:52:10 +0000 Subject: [PATCH 2/2] ci: trigger checks