diff --git a/CHANGELOG.md b/CHANGELOG.md index 0346323..9aa152c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## v2.2.2(2026-01-05) + +### Fixed + +- Fix a bug in GFM alert block parsing where alert types were case-sensitive. +- Fix GFM alert regex to disallow content on header line. + ## v2.2.1(2025-10-13) ### Changed diff --git a/marko/__init__.py b/marko/__init__.py index 4aae580..a21e6dd 100644 --- a/marko/__init__.py +++ b/marko/__init__.py @@ -23,7 +23,7 @@ from .block import Document from .parser import ElementType -__version__ = "2.2.1" +__version__ = "2.2.2" class SetupDone(Exception): diff --git a/marko/ext/gfm/elements.py b/marko/ext/gfm/elements.py index fa98289..2edcaeb 100644 --- a/marko/ext/gfm/elements.py +++ b/marko/ext/gfm/elements.py @@ -225,11 +225,13 @@ class Alert(block.Quote): @classmethod def match(cls, source): - return source.expect_re(r" {,3}>\s*\[\!(WARNING|NOTE|TIP|IMPORTANT|CAUTION)\]") + return source.expect_re( + r"(?im) {,3}>\s*\[\!(WARNING|NOTE|TIP|IMPORTANT|CAUTION)\]\s*$" + ) @classmethod def parse(cls, source): - alert_type = source.match.group(1) + alert_type = source.match.group(1).upper() source.next_line(require_prefix=False) source.consume() state = cls(alert_type) diff --git a/tests/test_ext.py b/tests/test_ext.py index d20e2db..0b3f04e 100644 --- a/tests/test_ext.py +++ b/tests/test_ext.py @@ -1,3 +1,5 @@ +import pytest + from marko import Markdown @@ -106,20 +108,32 @@ def setup_method(self): self.md_ast = Markdown(renderer=ASTRenderer, extensions=[GFM]) self.md_html = Markdown(extensions=[GFM]) - def test_alert_ast(self): - text = "> [!WARNING]\n> Foo bar\n> Bar\n" + @pytest.mark.parametrize( + "alert_type", ["WARNING", "CAUTION", "TIP", "warning", "Caution"] + ) + def test_alert_ast(self, alert_type): + text = f"> [!{alert_type}]\n> Foo bar\n> Bar\n" ast = self.md_ast(text) admon = ast["children"][0] assert admon["element"] == "alert" - assert admon["alert_type"] == "WARNING" + assert admon["alert_type"] == alert_type.upper() inner = admon["children"][0]["children"] assert inner[0]["children"] == "Foo bar" assert inner[1]["element"] == "line_break" assert inner[2]["children"] == "Bar" - def test_alert_html(self): - text = "> [!WARNING]\n> Foo bar\n> Bar\n" + @pytest.mark.parametrize( + "alert_type", ["WARNING", "CAUTION", "TIP", "warning", "Caution"] + ) + def test_alert_html(self, alert_type): + text = f"> [!{alert_type}]\n> Foo bar\n> Bar\n" html = self.md_html(text) - assert '
' in html - assert "Warning
" in html + assert f'' in html + assert f"{alert_type.title()}
" in html assert "Foo bar\nBar
" in html + + def test_alert_disallow_content_on_header_line(self): + text = "> [!NOTE] This is not allowed.\n> Foo bar\n" + html = self.md_html(text) + # Should be treated as a normal blockquote. + assert "\n[!NOTE] This is not allowed." in html