From 700839f77f9b87ee3663f2621d64cec299ec772d Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Thu, 6 Nov 2025 07:31:45 -0800 Subject: [PATCH] Handle unclosed HTML tags inside a markdown element This fixes an issue where it was panicking due to an unbalanced HTML tag when exiting a markdown element. The problem was that the tag stack was left non-empty when processing was finished due to `end_tag` being out of sync with the pulldown-cmark event tags. There really should be better validation that the stack is in sync and balanced, but this should address the main culprit of the interplay of raw HTML tags and pulldown-cmark events. --- crates/mdbook-html/src/html/tree.rs | 24 ++++++++++++++++++++++-- tests/testsuite/rendering.rs | 19 +++++++++---------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/crates/mdbook-html/src/html/tree.rs b/crates/mdbook-html/src/html/tree.rs index e4da7ef6..0c7c2e4d 100644 --- a/crates/mdbook-html/src/html/tree.rs +++ b/crates/mdbook-html/src/html/tree.rs @@ -583,8 +583,28 @@ where } fn end_tag(&mut self, tag: TagEnd) { - // TODO: This should validate that the event stack is - // properly synchronized with the tag stack. + // TODO: This should validate that the event stack is properly + // synchronized with the tag stack. That, would likely require keeping + // a parallel "expected end tag" with the tag stack, since mapping a + // pulldown-cmark event tag to an HTML tag isn't always clear. + // + // Check for unclosed HTML tags when exiting a markdown event. + while let Some(node_id) = self.tag_stack.last() { + let node = self.tree.get(*node_id).unwrap().value(); + let Node::Element(el) = node else { + break; + }; + if !el.was_raw { + break; + } + warn!( + "unclosed HTML tag `<{}>` found in `{}` while exiting {tag:?}\n\ + HTML tags must be closed before exiting a markdown element.", + el.name.local, + self.options.path.display(), + ); + self.pop(); + } self.pop(); match tag { TagEnd::TableHead => { diff --git a/tests/testsuite/rendering.rs b/tests/testsuite/rendering.rs index ea75fbd0..421aa5d4 100644 --- a/tests/testsuite/rendering.rs +++ b/tests/testsuite/rendering.rs @@ -290,18 +290,17 @@ fn heading_with_unbalanced_html() { BookTest::init(|_| {}) .change_file("src/chapter_1.md", "### Option") .run("build", |cmd| { - cmd.expect_failure().expect_stderr(str![[r#" + cmd.expect_stderr(str![[r#" INFO Book building has started INFO Running the html backend - -thread 'main' ([..]) panicked at crates/mdbook-html/src/html/tree.rs:[..] -internal error: expected empty tag stack. - - path: `chapter_1.md` -element=Element { name: QualName { prefix: None, ns: Atom('http://www.w3.org/1999/xhtml' type=static), local: Atom('h3' type=inline) }, attrs: {}, self_closing: false, was_raw: false } -note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + WARN unclosed HTML tag `` found in `chapter_1.md` while exiting Heading(H3) +HTML tags must be closed before exiting a markdown element. + INFO HTML book written to `[ROOT]/book` "#]]); - }); - // .check_main_file("book/chapter_1.html", str![[""]]); + }) + .check_main_file( + "book/chapter_1.html", + str![[r##"

Option

"##]], + ); }