From 1e190137c3bcca27ceebd95d6737696dbb875d8c Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 5 Nov 2025 11:14:39 -0800 Subject: [PATCH] Add a check for unclosed elements on the stack This checks for any unclosed elements when processing is finished. This is intended to detect invalid HTML in the source, or bugs in the tree builder. Raw HTML elements generate a warning (which in the future will be a configurable lint). All other sync errors are internal errors as they are not expected, and it would be helpful to know if they ever happen. --- crates/mdbook-html/src/html/tree.rs | 35 +++++++++++++++++++++++++++++ tests/testsuite/rendering.rs | 3 +++ 2 files changed, 38 insertions(+) diff --git a/crates/mdbook-html/src/html/tree.rs b/crates/mdbook-html/src/html/tree.rs index 4184740d..383c75b4 100644 --- a/crates/mdbook-html/src/html/tree.rs +++ b/crates/mdbook-html/src/html/tree.rs @@ -378,6 +378,7 @@ where } } } + self.finish_stack(); self.collect_footnote_defs(); } @@ -736,6 +737,40 @@ where output } + /// Deals with any unclosed elements on the stack. + fn finish_stack(&mut self) { + while let Some(node_id) = self.tag_stack.pop() { + let node = self.tree.get(node_id).unwrap().value(); + match node { + Node::Fragment => {} + Node::Element(el) => { + if el.was_raw { + warn!( + "unclosed HTML tag `<{}>` found in `{}`", + el.name.local, + self.options.path.display() + ); + } else { + panic!( + "internal error: expected empty tag stack.\n + path: `{}`\n\ + element={el:?}", + self.options.path.display() + ); + } + } + node => { + panic!( + "internal error: expected empty tag stack.\n + path: `{}`\n\ + node={node:?}", + self.options.path.display() + ); + } + } + } + } + /// Appends a new footnote reference. fn footnote_reference(&mut self, name: CowStr<'event>) { let len = self.footnote_numbers.len() + 1; diff --git a/tests/testsuite/rendering.rs b/tests/testsuite/rendering.rs index 961710ed..f77f0840 100644 --- a/tests/testsuite/rendering.rs +++ b/tests/testsuite/rendering.rs @@ -252,6 +252,9 @@ fn unclosed_html_tags() { cmd.expect_stderr(str![[r#" INFO Book building has started INFO Running the html backend + WARN unclosed HTML tag `` found in `chapter_1.md` + WARN unclosed HTML tag `` found in `chapter_1.md` + WARN unclosed HTML tag `
` found in `chapter_1.md` INFO HTML book written to `[ROOT]/book` "#]]);