fix(markdown): close a definition list before the paragraph that follows it

This commit is contained in:
2026-09-28 15:03:10 +02:00
parent cf942442ef
commit 2913aa4eb0
3 changed files with 65 additions and 0 deletions
+4
View File
@@ -7,6 +7,10 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [development]
### Fixed
- A paragraph that follows a definition list, separated from it by a blank line, is no longer swallowed into the `dl` element: the list ends before it, and terms after the paragraph open a list of their own.
### Added
-
@@ -81,6 +81,10 @@ func TestDefinitionLists(t *testing.T) {
"<dl>\n<dt>Term</dt>\n<dd>\n<p>Def</p>\n<p>more</p>\n</dd>\n</dl>\n"},
{"definition inline content", "Term\n: A *bold* claim",
"<dl>\n<dt>Term</dt>\n<dd>A <em>bold</em> claim</dd>\n</dl>\n"},
{"definition paragraph after list", "Term\n: Definition\n\nParagraph after.",
"<dl>\n<dt>Term</dt>\n<dd>Definition</dd>\n</dl>\n<p>Paragraph after.</p>\n"},
{"definition paragraph between lists", "Term\n: Def\n\nBetween\n\nTerm 2\n: Def 2",
"<dl>\n<dt>Term</dt>\n<dd>Def</dd>\n</dl>\n<p>Between</p>\n<dl>\n<dt>Term 2</dt>\n<dd>Def 2</dd>\n</dl>\n"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
+57
View File
@@ -614,6 +614,9 @@ func (p *parser) finalise(n *Node) {
n.content = trimTrailingBlankLines(n.content)
}
case kindList, kindDefList:
if n.kind == kindDefList {
p.liftLeakedTerms(n)
}
n.tight = !blocksAreLoose(n.children)
case kindDocument:
p.gatherFootnotes(n)
@@ -623,6 +626,60 @@ func (p *parser) finalise(n *Node) {
}
}
// liftLeakedTerms moves the paragraphs that never became terms out of a
// definition list. A paragraph may sit directly inside the list as a
// term waiting for its definition line; when that line never arrives the
// paragraph is prose after the list, and the list ends before it, since
// a dl element carries nothing but terms and definitions. Terms and
// definitions after the leak form a list of their own.
func (p *parser) liftLeakedTerms(dl *Node) {
leak := -1
for i, c := range dl.children {
if c.kind == kindParagraph {
leak = i
break
}
}
if leak < 0 {
return
}
parent := dl.parent
base := 0
for i, c := range parent.children {
if c == dl {
base = i + 1
break
}
}
tail := dl.children[leak:]
dl.children = dl.children[:leak]
insert := func(n *Node) {
parent.children = append(parent.children, nil)
copy(parent.children[base+1:], parent.children[base:])
parent.children[base] = n
base++
n.parent = parent
}
var regroup *Node
for _, c := range tail {
switch c.kind {
case kindParagraph:
insert(c)
regroup = nil
case kindDefTerm, kindDefItem:
if regroup == nil {
regroup = &Node{kind: kindDefList, parent: parent}
insert(regroup)
}
c.parent = regroup
regroup.children = append(regroup.children, c)
}
}
if regroup != nil {
regroup.tight = !blocksAreLoose(regroup.children)
}
}
// trimTrailingBlankLines removes the trailing blank lines of an indented
// code block. Every content line carries its newline, so the result of a
// non-empty block ends with exactly one.