fix(markdown): parse escaped alerts by default (#39633)

Previously, recognition of escaped alert markers such as `\[!NOTE\]`
accidentally depended on enabling the optional `inline-parentheses` math
setting, resulting in such escaped alerts not rendering properly.
Goldmark splits the escaped marker differently based on the setting
being enabled or disabled, and only one form was handled. This commit
handles both forms.

For example, in a README or issue:

```
> \[!NOTE\]
> text
```

Before this commit, we'd get an ordinary blockquote displaying literal
`[!NOTE]` followed by `text`. With this commit, we get the styled
admonition.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Sergio Benitezandwxiaoguang authored and GitHub committed 2026-10-08 13:47:17 +00:00
1 parent 8a553332f7
commit ab9bcd6a79
3 files changed
+20 -9

No files matched your search

@@ -9,7 +9,9 @@ import (
"gitea.dev/modules/markup"
"gitea.dev/modules/markup/markdown"
"gitea.dev/modules/setting"
"gitea.dev/modules/svg"
"gitea.dev/modules/test"
"github.com/stretchr/testify/assert"
"golang.org/x/text/cases"
@@ -22,6 +24,7 @@ func TestAttention(t *testing.T) {
defer svg.MockIcon("octicon-report")()
defer svg.MockIcon("octicon-alert")()
defer svg.MockIcon("octicon-stop")()
defer test.MockVariableValue(&setting.Markdown.MathCodeBlockOptions, setting.MarkdownMathCodeBlockOptions{})()
test := func(input, expected string) {
result, err := markdown.RenderString(markup.NewTestRenderContext(), input)
@@ -47,8 +50,16 @@ func TestAttention(t *testing.T) {
test(`> [!warning]`, renderAttention("warning", "octicon-alert")+"\n</blockquote>")
test(`> [!caution]`, renderAttention("caution", "octicon-stop")+"\n</blockquote>")
// escaped by mdformat
test(`> \[!NOTE\]`, renderAttention("note", "octicon-info")+"\n</blockquote>")
for _, parseInlineParentheses := range []bool{false, true} {
setting.Markdown.MathCodeBlockOptions.ParseInlineParentheses = parseInlineParentheses
test("> \\[!NOTE\\]\n> text", renderAttention("note", "octicon-info")+"\n<p>text</p>\n</blockquote>")
test(`> \[!UNKNOWN\]`, "<blockquote>\n<p>[!UNKNOWN]</p>\n</blockquote>")
test(`> \[NOTE\]`, "<blockquote>\n<p>[NOTE]</p>\n</blockquote>")
test(`> \[!NOTE`, "<blockquote>\n<p>[!NOTE</p>\n</blockquote>")
test(`> [!NOTE\]`, "<blockquote>\n<p>[!NOTE]</p>\n</blockquote>")
test("> \\[!\n> NOTE\\]", "<blockquote>\n<p>[!\nNOTE]</p>\n</blockquote>")
test("> \\[\n> !NOTE\\]", "<blockquote>\n<p>[\n!NOTE]</p>\n</blockquote>")
}
// legacy GitHub style
test(`> **warning**`, renderAttention("warning", "octicon-alert")+"\n</blockquote>")
@@ -17,7 +17,7 @@ import (
const nl = "\n"
func TestMathRender(t *testing.T) {
setting.Markdown.MathCodeBlockOptions = setting.MarkdownMathCodeBlockOptions{ParseInlineDollar: true, ParseInlineParentheses: true}
defer test.MockVariableValue(&setting.Markdown.MathCodeBlockOptions, setting.MarkdownMathCodeBlockOptions{ParseInlineDollar: true, ParseInlineParentheses: true})()
testcases := []struct {
testcase string
expected string
@@ -122,7 +122,7 @@ func TestMathRender(t *testing.T) {
}
func TestMathRenderBlockIndent(t *testing.T) {
setting.Markdown.MathCodeBlockOptions = setting.MarkdownMathCodeBlockOptions{ParseBlockDollar: true, ParseBlockSquareBrackets: true}
defer test.MockVariableValue(&setting.Markdown.MathCodeBlockOptions, setting.MarkdownMathCodeBlockOptions{ParseBlockDollar: true, ParseBlockSquareBrackets: true})()
testcases := []struct {
name string
testcase string
@@ -257,8 +257,7 @@ x
}
func TestMathRenderOptions(t *testing.T) {
setting.Markdown.MathCodeBlockOptions = setting.MarkdownMathCodeBlockOptions{}
defer test.MockVariableValue(&setting.Markdown.MathCodeBlockOptions)
defer test.MockVariableValue(&setting.Markdown.MathCodeBlockOptions, setting.MarkdownMathCodeBlockOptions{})()
test := func(t *testing.T, expected, input string) {
res, err := RenderString(markup.NewTestRenderContext(), input)
assert.NoError(t, err)
@@ -62,7 +62,7 @@ func (g *ASTTransformer) extractBlockquoteAttention2(firstParagraph ast.Node, re
return "", nil
}
node1, ok := firstParagraph.FirstChild().(*ast.Text)
if !ok {
if !ok || node1.SoftLineBreak() || node1.HardLineBreak() {
return "", nil
}
node2, ok := node1.NextSibling().(*ast.Text)
@@ -71,8 +71,9 @@ func (g *ASTTransformer) extractBlockquoteAttention2(firstParagraph ast.Node, re
}
val1 := string(node1.Segment.Value(reader.Source()))
val2 := string(node2.Segment.Value(reader.Source()))
if strings.HasPrefix(val1, `\[!`) && val2 == `\]` {
attentionType := strings.ToLower(val1[3:])
// Inline math options change where Goldmark splits the escaped marker.
if inner, ok := strings.CutPrefix(val1+val2, `\[!`); ok && strings.HasSuffix(val2, `\]`) {
attentionType := strings.ToLower(strings.TrimSuffix(inner, `\]`))
if g.attentionTypes.Contains(attentionType) {
return attentionType, []ast.Node{node1, node2}
}