From b9755183f25a6fcdde723e6d402b0d9b4f7a6710 Mon Sep 17 00:00:00 2001 From: facelessuser Date: Wed, 21 Oct 2026 06:38:06 -0600 Subject: [PATCH] Fix uneven backtick handling 1. Ensure start is only adjusted with the longest closing code span is less than the opening and greater than 0. 2. Remove dead code as we should always find an opening. 3. `find_code_span` will always return an opening. End will be `None` if a match could not be found. 4. Ensure the beginning backticks are not parsed again if no end was found. Fixes #1668 --- docs/changelog.md | 5 +++++ markdown/inlinepatterns.py | 20 ++++++++------------ tests/test_syntax/inline/test_code.py | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index dcb63628..a6616000 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -12,6 +12,11 @@ and this project adheres to the [Python Version Specification](https://packaging.python.org/en/latest/specifications/version-specifiers/). See the [Contributing Guide](contributing.md) for details. +## [Unreleased] + +* Fix regression in `BacktickInlineProcessor` where text could be lost when opening and closing backticks did not + match (#1668). + ## [3.11.2] - 2026-10-19 ### Fixed diff --git a/markdown/inlinepatterns.py b/markdown/inlinepatterns.py index 785585d0..ec9c7cb1 100644 --- a/markdown/inlinepatterns.py +++ b/markdown/inlinepatterns.py @@ -413,7 +413,7 @@ def __init__(self, pattern: str): self.tag = 'code' """ The tag of the rendered element. """ - def find_code_spans(self, start: int, text: str) -> tuple[int, int] | None: + def find_code_spans(self, start: int, text: str) -> tuple[int, int | None]: """Find code spans.""" last = len(text) @@ -424,10 +424,6 @@ def find_code_spans(self, start: int, text: str) -> tuple[int, int] | None: max_ticks += 1 start += 1 - if not max_ticks: # pragma: no cover - # This is not ever expected to happen. - return None - longest_span = 0 end = 0 @@ -453,11 +449,11 @@ def find_code_spans(self, start: int, text: str) -> tuple[int, int] | None: # Since we didn't find an exact matching start and end, # adjust start to match the largest end we could calculate. - if longest_span: + if 0 < longest_span < max_ticks: return start - (max_ticks - longest_span), end - longest_span # We could not find a suitable pairing - return None + return start + max_ticks, None def handleMatch(self, m: re.Match[str], data: str) -> tuple[etree.Element | str | None, int | None, int | None]: """ @@ -472,13 +468,13 @@ def handleMatch(self, m: re.Match[str], data: str) -> tuple[etree.Element | str return m.group(1).replace('\\\\', self.ESCAPED_BSLASH), m.start(0), m.end(0) begin = m.start(0) - result = self.find_code_spans(begin, data) - if result is not None: - start, end = result + start, end = self.find_code_spans(begin, data) + if end is not None: el = etree.Element(self.tag) el.text = util.AtomicString(util.code_escape(data[start:end].strip())) - return el, begin, result[1] + (start - begin) - return None, None, None + return el, begin, end + (start - begin) + # Skip over backtick group as we could find no match + return None, begin, start class DoubleTagPattern(SimpleTagPattern): # pragma: no cover diff --git a/tests/test_syntax/inline/test_code.py b/tests/test_syntax/inline/test_code.py index c7ccc4bf..cf54de72 100644 --- a/tests/test_syntax/inline/test_code.py +++ b/tests/test_syntax/inline/test_code.py @@ -79,3 +79,18 @@ def test_noname_tag(self): """ ) ) + + def test_bigger_end(self): + + # https://github.com/Python-Markdown/markdown/issues/1668 + self.assertMarkdownRenders( + 'a ``` b ````` c', + '

a ``` b ````` c

' + ) + + def test_smaller_end(self): + + self.assertMarkdownRenders( + 'a ``` b `` c', + '

a ` b c

' + )