Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion marko/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from .block import Document
from .parser import ElementType

__version__ = "2.2.1"
__version__ = "2.2.2"


class SetupDone(Exception):
Expand Down
6 changes: 4 additions & 2 deletions marko/ext/gfm/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
28 changes: 21 additions & 7 deletions tests/test_ext.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

from marko import Markdown


Expand Down Expand Up @@ -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 '<blockquote class="alert alert-warning">' in html
assert "<p>Warning</p>" in html
assert f'<blockquote class="alert alert-{alert_type.lower()}">' in html
assert f"<p>{alert_type.title()}</p>" in html
assert "<p>Foo bar\nBar</p>" 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 "<blockquote>\n<p>[!NOTE] This is not allowed." in html