Skip to content

Commit 92f1b4d

Browse files
committed
fix: prevent mixed =/- chars in Setext-style headings (+tests)
- Fix SetextHeaderProcessor RE regex to require consistent underline chars (all '=' for h1, all '-' for h2), rejecting mixed =/- patterns - Add test case: h2 followed by h1 underline (=====) was incorrectly accepted and rendered as h2; now correctly rejected - Add 'setext' (lowercase) to .spell-dict to fix checkspelling CI
1 parent e7a0efb commit 92f1b4d

4 files changed

Lines changed: 60 additions & 1 deletion

File tree

.spell-dict

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,4 @@ Serafim
203203
overridable
204204
unescaped
205205
APIs
206+
setext

docs/changelog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ and this project adheres to the
1010
[Python Version Specification](https://packaging.python.org/en/latest/specifications/version-specifiers/).
1111
See the [Contributing Guide](contributing.md) for details.
1212

13+
## [Unreleased]
14+
15+
### Fixed
16+
17+
* Fix `SetextHeaderProcessor` regex to prevent mixed `=` and `-` chars in setext-style headers (#1606).
18+
1319
## [3.10.2] - 2026-02-09
1420

1521
### Fixed

markdown/blockprocessors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ class SetextHeaderProcessor(BlockProcessor):
494494
""" Process Setext-style Headers. """
495495

496496
# Detect Setext-style header. Must be first 2 lines of block.
497-
RE = re.compile(r'^.*?\n[=-]+[ ]*(\n|$)', re.MULTILINE)
497+
RE = re.compile(r'^.*?\n(?:=+|-+)[ ]*(\n|$)', re.MULTILINE)
498498

499499
def test(self, parent: etree.Element, block: str) -> bool:
500500
return bool(self.RE.match(block))

tests/test_syntax/blocks/test_headers.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,58 @@ def test_setext_h2_followed_by_p(self):
109109

110110
# TODO: fix this
111111
# see https://johnmacfarlane.net/babelmark2/?normalize=1&text=Paragraph%0AAn+H1%0A%3D%3D%3D%3D%3D
112+
113+
def test_setext_mixed_chars_not_h1(self):
114+
"""Mixed = and - chars should not form a valid H1."""
115+
self.assertMarkdownRenders(
116+
self.dedent(
117+
"""
118+
This is not an H1
119+
=-
120+
"""
121+
),
122+
self.dedent(
123+
"""
124+
<p>This is not an H1
125+
=-</p>
126+
"""
127+
)
128+
)
129+
130+
def test_setext_mixed_chars_not_h2(self):
131+
"""Mixed - and = chars should not form a valid H2."""
132+
self.assertMarkdownRenders(
133+
self.dedent(
134+
"""
135+
This is not an H2
136+
-=
137+
"""
138+
),
139+
self.dedent(
140+
"""
141+
<p>This is not an H2
142+
-=</p>
143+
"""
144+
)
145+
)
146+
147+
def test_setext_mixed_multiple_chars(self):
148+
"""Multiple mixed = and - chars should not form a valid H1/H2."""
149+
self.assertMarkdownRenders(
150+
self.dedent(
151+
"""
152+
Not a Header
153+
=-=-
154+
"""
155+
),
156+
self.dedent(
157+
"""
158+
<p>Not a Header
159+
=-=-</p>
160+
"""
161+
)
162+
)
163+
112164
@unittest.skip('This is broken in Python-Markdown')
113165
def test_p_followed_by_setext_h1(self):
114166
self.assertMarkdownRenders(

0 commit comments

Comments
 (0)