From afbc9b493d32e6f8d3683eb0f1ea0a059463398a Mon Sep 17 00:00:00 2001 From: anticipate218 <3481402509@qq.com> Date: Thu, 12 Nov 2026 09:28:32 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20FIX:=20Honor=20empty=20rule=20li?= =?UTF-8?q?sts=20in=20custom=20presets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Forward explicitly empty rules and rules2 lists to enableOnly so custom presets can disable an entire parser chain. Keep missing and None values unchanged; cover all four chains and literal inline rendering. Assisted-by: OpenAI Codex --- CHANGELOG.md | 4 ++++ docs/using.md | 4 ++++ markdown_it/main.py | 4 ++-- tests/test_api/test_main.py | 30 +++++++++++++++++++++++++++++- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c411d126..54697f20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## Unreleased + +* 🐛 Honor empty `rules` and `rules2` lists in custom presets by disabling every rule in the specified chain. + ## 4.3.0 - 2026-11-10 This release fixes two quadratic-time parsing paths that let crafted input stall rendering for minutes (security), diff --git a/docs/using.md b/docs/using.md index fd242313..1f5d5a33 100644 --- a/docs/using.md +++ b/docs/using.md @@ -67,6 +67,10 @@ from markdown_it.presets import zero zero.make() ``` +In a custom preset's `components`, an empty `rules` list disables all rules in that component. +The same applies to `rules2` for inline post-processing rules. +Omitting either list leaves the corresponding rules unchanged. + ```{jupyter-execute} md = MarkdownIt("zero") md.options diff --git a/markdown_it/main.py b/markdown_it/main.py index 33088ef5..e204f3fc 100644 --- a/markdown_it/main.py +++ b/markdown_it/main.py @@ -131,10 +131,10 @@ def configure( if "components" in config: for name, component in config["components"].items(): rules = component.get("rules", None) - if rules: + if rules is not None: self[name].ruler.enableOnly(rules) rules2 = component.get("rules2", None) - if rules2: + if rules2 is not None: self[name].ruler2.enableOnly(rules2) return self diff --git a/tests/test_api/test_main.py b/tests/test_api/test_main.py index 74c7b0f8..9a23c60e 100644 --- a/tests/test_api/test_main.py +++ b/tests/test_api/test_main.py @@ -4,7 +4,7 @@ import pytest -from markdown_it import MarkdownIt +from markdown_it import MarkdownIt, presets from markdown_it.token import Token if TYPE_CHECKING: @@ -107,6 +107,34 @@ def test_override_options(): assert md.options["maxNesting"] == 99 +def test_preset_empty_rules() -> None: + """An empty preset rule list disables the entire specified chain.""" + for component, key, chain in ( + ("core", "rules", "core"), + ("block", "rules", "block"), + ("inline", "rules", "inline"), + ("inline", "rules2", "inline2"), + ): + config = presets.commonmark.make() + config["components"][component][key] = [] + + md = MarkdownIt(config) + + assert md.get_active_rules()[chain] == [] + + +def test_preset_empty_inline_rules_render_literal_text() -> None: + """Custom presets can disable all inline markup without individual names.""" + config = presets.commonmark.make() + config["components"]["inline"]["rules"] = [] + + md = MarkdownIt(config) + + assert md.renderInline("**bold** [link](https://example.com)") == ( + "**bold** [link](https://example.com)" + ) + + def test_gfm_like2_tasklists_editable(): md_default = MarkdownIt("gfm-like2", GFM_LIKE2_OPTIONS) assert md_default.options["tasklists_editable"] is False