From c334a3e47e6565469344154f966cf0eb9aca0de3 Mon Sep 17 00:00:00 2001 From: Waylan Limberg Date: Mon, 29 Jan 2024 13:42:51 -0500 Subject: [PATCH] Ensure lines after heading in loose list are properly detabbed This is a weird edge case. Normally, detabbing would be handled by the `ListIndentProcessor`. However, in this one case, that class's `get_level` method would need to return a different result than in any other case. As there is no way to easily determine this specific case from that class, we make the adjustment directly in the `HashHeaderProcessor` class. Fixes #1433. --- .spell-dict | 1 + docs/changelog.md | 1 + markdown/blockprocessors.py | 5 + tests/test_syntax/blocks/test_ul.py | 137 ++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+) create mode 100644 tests/test_syntax/blocks/test_ul.py diff --git a/.spell-dict b/.spell-dict index 13cdea5b..ae124542 100644 --- a/.spell-dict +++ b/.spell-dict @@ -32,6 +32,7 @@ customizable dedent deliminators deregister +detabbed Dmitry docdata ElementTree diff --git a/docs/changelog.md b/docs/changelog.md index 66636067..884320f7 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed * Include `scripts/*.py` in the generated source tarballs (#1430). +* Ensure lines after heading in loose list are properly detabbed (#1443). ## [3.5.2] -- 2024-01-10 diff --git a/markdown/blockprocessors.py b/markdown/blockprocessors.py index d2020b9b..3ed4cf07 100644 --- a/markdown/blockprocessors.py +++ b/markdown/blockprocessors.py @@ -479,6 +479,11 @@ def run(self, parent: etree.Element, blocks: list[str]) -> None: h.text = m.group('header').strip() if after: # Insert remaining lines as first block for future parsing. + if self.parser.state.isstate('looselist'): + # This is a weird edge case where a header is a child of a loose list + # and there is no blank line after the header. To ensure proper + # parsing, the line(s) after need to be detabbed. See #1443. + after = self.looseDetab(after) blocks.insert(0, after) else: # pragma: no cover # This should never happen, but just in case... diff --git a/tests/test_syntax/blocks/test_ul.py b/tests/test_syntax/blocks/test_ul.py new file mode 100644 index 00000000..f90d43a2 --- /dev/null +++ b/tests/test_syntax/blocks/test_ul.py @@ -0,0 +1,137 @@ +""" +Python Markdown + +A Python implementation of John Gruber's Markdown. + +Documentation: https://python-markdown.github.io/ +GitHub: https://github.com/Python-Markdown/markdown/ +PyPI: https://pypi.org/project/Markdown/ + +Started by Manfred Stienstra (http://www.dwerg.net/). +Maintained for a few years by Yuri Takhteyev (http://www.freewisdom.org). +Currently maintained by Waylan Limberg (https://github.com/waylan), +Dmitry Shachnev (https://github.com/mitya57) and Isaac Muse (https://github.com/facelessuser). + +Copyright 2007-2024 The Python Markdown Project (v. 1.7 and later) +Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) +Copyright 2004 Manfred Stienstra (the original version) + +License: BSD (see LICENSE.md for details). +""" + +import unittest +from markdown.test_tools import TestCase + + +class TestUnorderedLists(TestCase): + + # TODO: Move legacy tests here + + def test_header_and_paragraph_no_blank_line_loose_list(self): + self.assertMarkdownRenders( + self.dedent( + """ + - ### List 1 + Entry 1.1 + + - ### List 2 + Entry 2.1 + """ + ), + self.dedent( + """ + + """ + ) + ) + + # Note: This is strange. Any other element on first line of list item would get very + # different behavior. However, as a heading can only ever be one line, this is the + # correct behavior. In fact, `markdown.pl` behaves this way with no indentation. + def test_header_and_paragraph_no_blank_line_loose_list_no_indent(self): + self.assertMarkdownRenders( + self.dedent( + """ + - ### List 1 + Entry 1.1 + + - ### List 2 + Entry 2.1 + """ + ), + self.dedent( + """ + + """ + ) + ) + + # TODO: Possibly change this behavior. While this test follows the behavior + # of `markdown.pl`, it is likely surprising to most users. In fact, actual + # behavior is to return the same results as for a loose list. + @unittest.skip('This behaves as a loose list in Python-Markdown') + def test_header_and_paragraph_no_blank_line_tight_list(self): + self.assertMarkdownRenders( + self.dedent( + """ + - ### List 1 + Entry 1.1 + - ### List 2 + Entry 2.1 + """ + ), + self.dedent( + """ + + """ + ) + ) + + # TODO: Possibly change this behavior. While this test follows the behavior + # of `markdown.pl`, it is likely surprising to most users. In fact, actual + # behavior is to return the same results as for a loose list. + @unittest.skip('This behaves as a loose list in Python-Markdown') + def test_header_and_paragraph_no_blank_line_tight_list_no_indent(self): + self.assertMarkdownRenders( + self.dedent( + """ + - ### List 1 + Entry 1.1 + - ### List 2 + Entry 2.1 + """ + ), + self.dedent( + """ + + """ + ) + )