diff --git a/src/mkdocstrings/extension.py b/src/mkdocstrings/extension.py index 139030ba..f7634a28 100644 --- a/src/mkdocstrings/extension.py +++ b/src/mkdocstrings/extension.py @@ -231,17 +231,26 @@ def _process_block( class _PostProcessor(Treeprocessor): def run(self, root: Element) -> None: + self._remove_duplicated_headings(root) + + def _remove_duplicated_headings(self, parent: Element) -> bool: carry_text = "" - for el in reversed(root): # Reversed mainly for the ability to mutate during iteration. + found = False + for el in reversed(parent): # Reversed mainly for the ability to mutate during iteration. if el.tag == "div" and el.get("class") == "mkdocstrings": # Delete the duplicated headings along with their container, but keep the text (i.e. the actual HTML). carry_text = (el.text or "") + carry_text - root.remove(el) + parent.remove(el) + found = True elif carry_text: el.tail = (el.tail or "") + carry_text carry_text = "" + elif self._remove_duplicated_headings(el): + found = True + break if carry_text: - root.text = (root.text or "") + carry_text + parent.text = (parent.text or "") + carry_text + return found class MkdocstringsExtension(Extension): diff --git a/tests/test_extension.py b/tests/test_extension.py index 8c687629..2d50ef4d 100644 --- a/tests/test_extension.py +++ b/tests/test_extension.py @@ -150,3 +150,21 @@ def test_use_options_yaml_key(ext_markdown: Markdown) -> None: """Check that using the 'options' YAML key works as expected.""" assert "h1" in ext_markdown.convert("::: tests.fixtures.headings\n options:\n heading_level: 1") assert "h1" not in ext_markdown.convert("::: tests.fixtures.headings\n options:\n heading_level: 2") + + +@pytest.mark.parametrize("ext_markdown", [{"markdown_extensions": [{"pymdownx.tabbed": {"alternate_style": True}}]}], indirect=["ext_markdown"]) +def test_removing_duplicated_headings(ext_markdown: Markdown) -> None: + """Assert duplicated headings are removed from the output.""" + output = ext_markdown.convert( + dedent( + """ + === "Tab A" + + ::: tests.fixtures.headings + + """, + ), + ) + assert output.count("Foo") == 1 + assert output.count("Bar") == 1 + assert output.count("Baz") == 1