Skip to content

Commit 32b38e8

Browse files
fix: Markdown.ToMd preserves indented code blocks as fenced code blocks
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>
1 parent 142d4b0 commit 32b38e8

3 files changed

Lines changed: 40 additions & 6 deletions

File tree

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* 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)
88
* Fix `Markdown.ToMd` serialising italic spans with asterisks incorrectly as bold spans. [#1102](https://github.com/fsprojects/FSharp.Formatting/pull/1102)
99
* Fix `Markdown.ToMd` serialising ordered list items with incorrect numbering and formatting. [#1102](https://github.com/fsprojects/FSharp.Formatting/pull/1102)
10+
* 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.
1011

1112
## [22.0.0-alpha.2] - 2026-03-13
1213

src/FSharp.Formatting.Markdown/MarkdownUtils.fs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,15 +145,15 @@ module internal MarkdownUtils =
145145
yield "-----------------------"
146146
yield ""
147147
| CodeBlock(code = code; fence = fence; language = language) ->
148-
match fence with
149-
| None -> ()
150-
| Some f -> yield f + language
148+
// Indented code blocks (fence = None) are serialised as fenced blocks so
149+
// that the round-trip is valid — raw indented code without a '> ' prefix
150+
// or 4-space indent would be parsed as a paragraph, not a code block.
151+
let f = defaultArg fence "```"
152+
yield f + language
151153

152154
yield code
153155

154-
match fence with
155-
| None -> ()
156-
| Some f -> yield f
156+
yield f
157157

158158
yield ""
159159
| ListBlock(Unordered, paragraphsl, _) ->

tests/FSharp.Markdown.Tests/Markdown.fs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,3 +1371,36 @@ let ``ToMd round-trip: indirect image with unresolved reference`` () =
13711371
let result = Markdown.ToMd(doc)
13721372
// When key is not resolved, should preserve the indirect form
13731373
result |> should contain "![alt text][unknown-ref]"
1374+
1375+
// --------------------------------------------------------------------------------------
1376+
// ToMd round-trip: indented code block (fence = None) — issue #fix-tomd-indented-codeblock
1377+
// --------------------------------------------------------------------------------------
1378+
1379+
[<Test>]
1380+
let ``ToMd round-trip: indented code block is preserved as a code block`` () =
1381+
// An indented code block (4-space indent) is serialised as a fenced block to
1382+
// guarantee the round-trip: outputting bare code without any fence would cause
1383+
// re-parsing to produce a paragraph instead of a code block.
1384+
let input = " let x = 1\n let y = 2"
1385+
let doc = Markdown.Parse(input)
1386+
// The parser should have produced a CodeBlock, not a Paragraph
1387+
let cbs =
1388+
doc.Paragraphs
1389+
|> List.choose (function
1390+
| CodeBlock _ as cb -> Some cb
1391+
| _ -> None)
1392+
1393+
cbs |> should haveLength 1
1394+
// ToMd should produce a fenced form so the round-trip is valid
1395+
let result = Markdown.ToMd(doc, newline = "\n")
1396+
result |> should contain "```"
1397+
result |> should contain "let x = 1"
1398+
result |> should contain "let y = 2"
1399+
// The serialised form re-parses to a CodeBlock, not a Paragraph
1400+
let doc2 = Markdown.Parse(result)
1401+
1402+
doc2.Paragraphs
1403+
|> List.choose (function
1404+
| CodeBlock _ as cb -> Some cb
1405+
| _ -> None)
1406+
|> should haveLength 1

0 commit comments

Comments
 (0)