From 9a27d77bfa7a726d504b173aa9ce6b2219223369 Mon Sep 17 00:00:00 2001 From: Rupayon Haldar <80724680+rupayon123@users.noreply.github.com> Date: Sun, 4 Oct 2026 08:50:41 -0400 Subject: [PATCH] fix: stop parsing when plugin rules do not advance --- markdown_it/parser_block.py | 2 ++ markdown_it/parser_inline.py | 5 +++++ tests/test_parser_progress.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 tests/test_parser_progress.py diff --git a/markdown_it/parser_block.py b/markdown_it/parser_block.py index 50a7184c..c6de5c30 100644 --- a/markdown_it/parser_block.py +++ b/markdown_it/parser_block.py @@ -85,6 +85,8 @@ def tokenize(self, state: StateBlock, startLine: int, endLine: int) -> None: # - return True for rule in rules: if rule(state, line, endLine, False): + if state.line <= line: + raise RuntimeError("block rule didn't increment state.line") break # set state.tight if we had an empty line before current tag diff --git a/markdown_it/parser_inline.py b/markdown_it/parser_inline.py index af66a7fa..a7b07360 100644 --- a/markdown_it/parser_inline.py +++ b/markdown_it/parser_inline.py @@ -152,6 +152,8 @@ def skipToken(self, state: StateInline) -> None: ok = rule(state, True) state.level -= 1 if ok: + if state.pos <= pos: + raise RuntimeError("inline rule didn't increment state.pos") break else: # Too much nesting, just skip until the end of the paragraph. @@ -179,6 +181,7 @@ def tokenize(self, state: StateInline) -> None: maxNesting = state.md.options["maxNesting"] while state.pos < end: + pos = state.pos # Try all possible rules. # On success, rule should: # @@ -190,6 +193,8 @@ def tokenize(self, state: StateInline) -> None: for rule in rules: ok = rule(state, False) if ok: + if state.pos <= pos: + raise RuntimeError("inline rule didn't increment state.pos") break if ok: diff --git a/tests/test_parser_progress.py b/tests/test_parser_progress.py new file mode 100644 index 00000000..df4b7bbb --- /dev/null +++ b/tests/test_parser_progress.py @@ -0,0 +1,31 @@ +"""Regression tests for custom rules that claim a match without consuming input.""" + +import pytest + +from markdown_it import MarkdownIt +from markdown_it.rules_inline.state_inline import StateInline + + +def test_block_rule_must_advance_line() -> None: + md = MarkdownIt("commonmark") + md.block.ruler.before("paragraph", "stuck", lambda state, start, end, silent: True) + + with pytest.raises(RuntimeError, match=r"block rule didn't increment state\.line"): + md.parse("text\n") + + +def test_inline_rule_must_advance_position() -> None: + md = MarkdownIt("commonmark") + md.inline.ruler.before("text", "stuck", lambda state, silent: True) + + with pytest.raises(RuntimeError, match=r"inline rule didn't increment state\.pos"): + md.parse("text") + + +def test_inline_validation_rule_must_advance_position() -> None: + md = MarkdownIt("commonmark") + md.inline.ruler.before("text", "stuck", lambda state, silent: True) + state = StateInline("text", md, {}, []) + + with pytest.raises(RuntimeError, match=r"inline rule didn't increment state\.pos"): + md.inline.skipToken(state)