From 8e3e26f8e634ef7fd82f115500887f2e9dbb9551 Mon Sep 17 00:00:00 2001 From: Vinayak Mishra Date: Tue, 17 Feb 2026 16:36:24 +0545 Subject: [PATCH] fix: track fenced code state inside list items Lines inside a fenced code block within a list item (e.g. `- test` or `1. item`) were being misinterpreted as new list items or other block-level structures. This caused the list to break incorrectly whenever the fenced content happened to match list item syntax. Add a fenceMarker variable to the gatherlines loop that tracks whether we are inside a fenced code block. When inside a fence, lines are gathered verbatim, skipping structure detection. Only fences with indent > 0 are tracked (part of list item content). A fence at indent 0 ends the list as before. If an unclosed fence encounters a non-indented line, the fence state is abandoned to prevent swallowing subsequent list items. Fixes #346 --- block_test.go | 6 +++ parser/block.go | 36 ++++++++++++++++++ testdata/bug346.tests | 86 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 testdata/bug346.tests diff --git a/block_test.go b/block_test.go index 5ea69f55..9f0d4513 100644 --- a/block_test.go +++ b/block_test.go @@ -179,6 +179,12 @@ func TestCodeInList(t *testing.T) { doTestsParam(t, tests, TestParams{extensions: exts}) } +func TestBug346(t *testing.T) { + tests := readTestFile2(t, "bug346.tests") + exts := parser.CommonExtensions + doTestsParam(t, tests, TestParams{extensions: exts}) +} + func TestLists(t *testing.T) { tests := readTestFile2(t, "Lists.tests") exts := parser.CommonExtensions diff --git a/parser/block.go b/parser/block.go index 12404909..e0b99604 100644 --- a/parser/block.go +++ b/parser/block.go @@ -1409,6 +1409,9 @@ func (p *Parser) listItem(data []byte, flags *ast.ListType) int { // process the following lines containsBlankLine := false sublist := 0 + // track fenced code blocks inside list items so that lines within + // the fence are gathered verbatim (not misinterpreted as list items) + fenceMarker := "" gatherlines: for line < len(data) { @@ -1442,6 +1445,39 @@ gatherlines: chunk := data[line+indentIndex : i] + // track fenced code blocks inside list items; + // only track fences that are indented (part of the list item content), + // a fence at indent 0 ends the list (handled below) + if !isDefinitionList && p.extensions&FencedCode != 0 { + if fenceMarker != "" { + if indent == 0 { + // non-indented line while inside a fence means we + // left the list item content -- abandon the fence + fenceMarker = "" + } else { + // inside a fence: check for closing fence + _, marker := isFenceLine(chunk, nil, fenceMarker) + if marker != "" { + fenceMarker = "" + } + // gather the line verbatim, skip structure detection + if containsBlankLine { + containsBlankLine = false + raw.WriteByte('\n') + } + raw.Write(chunk) + line = i + continue + } + } else if indent > 0 { + // not inside a fence: check for opening fence (indented only) + _, marker := isFenceLine(chunk, nil, "") + if marker != "" { + fenceMarker = marker + } + } + } + // If there is a fence line (marking starting of a code block) // without indent do not process it as part of the list. // diff --git a/testdata/bug346.tests b/testdata/bug346.tests new file mode 100644 index 00000000..0b54067b --- /dev/null +++ b/testdata/bug346.tests @@ -0,0 +1,86 @@ +- test1 + + ``` + - test + ``` +- test2 + + ``` + - test + ``` +- test3 + + ``` + - test + ``` ++++ + ++++ +- item + + ``` + 1. not a list + ``` ++++ + ++++ +- item + + ``` + # not a heading + ``` ++++ + ++++ +- item + + ~~~ + - dash in tilde fence + ~~~ ++++ + ++++ +- item1 + + ``` + - should be code +- item2 ++++ +