Skip to content

Add GFM alerts extension#420

Open
ia3andy wants to merge 1 commit intocommonmark:mainfrom
ia3andy:gfm-alerts
Open

Add GFM alerts extension#420
ia3andy wants to merge 1 commit intocommonmark:mainfrom
ia3andy:gfm-alerts

Conversation

@ia3andy
Copy link
Contributor

@ia3andy ia3andy commented Mar 3, 2026

Note

I have used Claude to code most of it, I am currently reviewing the generated code which seems to be overall valid. I've requested to follow the pattern of existing extensions (and compare to other implementations of this feature).

Summary

  • Adds new extension module for GitHub Flavored Markdown alerts
  • Implements parser for alert blockquote syntax (> [!NOTE], > [!WARNING], etc.)
  • Includes HTML, Markdown, and text content renderers
  • Supports all five standard GFM alert types: NOTE, TIP, IMPORTANT, WARNING, CAUTION
  • Allows custom alert types via builder API
  • Includes comprehensive test coverage and documentation

Closes #327

@ia3andy ia3andy marked this pull request as draft March 3, 2026 16:05
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@ia3andy ia3andy marked this pull request as ready for review March 3, 2026 16:37
@ia3andy
Copy link
Contributor Author

ia3andy commented Mar 3, 2026

I've been through the code which seems good to me so I can say I am taking responsibilities for it like my own. Still, another set of eyes would be good :)

@ia3andy
Copy link
Contributor Author

ia3andy commented Mar 21, 2026

@robinst any thought ?

Copy link
Collaborator

@robinst robinst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some first comments, mostly around parsing and edge cases.

I was curious how GitHub implements this in cmark-gfm, and the answer seems to be that they don't:

github/cmark-gfm#350 (comment)

Alerts are not implemented in this project. They don't relate to their parser. They don't relate to syntax. So they're not here.

They are implemented as a post processing step on a sort of "DOM" version of the resulting document.

Can you explore what the implementation would look like as a PostProcessor instead? That has the advantage of not having to copy all the parsing logic from block quotes. (From the edge cases with nesting I commented about, it seems like they only look at top-level nodes.)


public class AlertBlockParser extends AbstractBlockParser {

private static final Pattern ALERT_PATTERN = Pattern.compile("^\\[!([A-Z]+)]$");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that in GitHub, they can be lowercase too:

> [!note]
> example

* Custom types must:
* <ul>
* <li>Be UPPERCASE (e.g., "INFO", "SUCCESS")</li>
* <li>Not conflict with standard GFM types (NOTE, TIP, IMPORTANT, WARNING, CAUTION)</li>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not? E.g. if someone wanted to localize the title to another language on output, being able to override the title would make sense, no?


@Test
public void emptyAlertWithNoContent() {
assertRendering("> [!NOTE]",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't match what GitHub does. It renders it as a normal block quote:

[!NOTE]


@Test
public void alertWithOnlyWhitespaceContent() {
assertRendering("> [!TIP]\n> \n> ",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't match either:

[!TIP]


@Test
public void lowercaseAlertTypeShouldNotMatch() {
assertRendering("> [!note]\n> This should be a blockquote",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already commented on this earlier, but this is an alert on GitHub:

> [!note]
> This should be a blockquote


@Test
public void mixedCaseAlertTypeShouldNotMatch() {
assertRendering("> [!Note]\n> This should be a blockquote",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This too:

> [!Note]
> This should be a blockquote


@Test
public void nestedAlertMarkersAreTreatedAsText() {
assertRendering("> [!NOTE]\n> This is a note\n> [!WARNING]\n> This is still part of the note",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test calls this nested, but nested is really this:

> [!NOTE]
> This is a note
>> [!WARNING]
>> This is still part of the note

Which GitHub, interestingly, renders as an alert with a normal blockquote inside. Even if we remove the outer alert, it stays as normal nested block quotes. Can you add those as tests?

This brings up another interesting case, can alerts be child blocks? E.g. this:

- > [!NOTE]
  > Test

That's rendered as a list item with a block quote, not a list item with an alert inside. I think the implementation in this PR doesn't handle that the same way.

String markdown = "> [!WARNING]\n> Be careful";
Node document = PARSER.parse(markdown);
String rendered = MARKDOWN_RENDERER.render(document);
assertThat(rendered).contains("[!WARNING]");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests should do roundtrip rendering like other markdown renderer tests do.

@ia3andy
Copy link
Contributor Author

ia3andy commented Mar 25, 2026

Thanks for the review! That all makes sense, will have a look soonish :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GFM admonition blocks (alerts)

2 participants