From d51f6bbf2aa536603e5c2368be88c155b7be559e Mon Sep 17 00:00:00 2001 From: youdie006 Date: Mon, 7 Sep 2026 11:23:25 +0900 Subject: [PATCH] Try every configured comment marker when scanning for callouts The inner loop in EscapeHTMLCallouts searches the configured markers, but breaks out of that search on the first non-match, so only Comments[0] can ever match and later entries are dead. Reordering the same set changes the output: with [// #] a line marked with # renders escaped, and with [# //] a line marked with // does. IsSafeURL and maybeAutoLink both scan their prefix lists to the end; this is the only one that stops at the first miss. The Parse: label and its continue Parse on the success path show the inner loop was meant to be a search. --- html/callouts_test.go | 24 ++++++++++++++++++++++++ html/renderer.go | 3 ++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/html/callouts_test.go b/html/callouts_test.go index 10124b98..b23f99bd 100644 --- a/html/callouts_test.go +++ b/html/callouts_test.go @@ -25,3 +25,27 @@ bliep bliep t.Error("callout code block not correctly parsed") } } + +func TestEscapeHTMLCalloutsMultipleComments(t *testing.T) { + code := []byte(`a //<<1>> +b #<<2>> +c <<9>> +`) + out := `a 1 +b 2 +c <<9>> +` + orders := [][][]byte{ + {[]byte("//"), []byte("#")}, + {[]byte("#"), []byte("//")}, + } + for _, comments := range orders { + buf := &bytes.Buffer{} + opts := RendererOptions{} + opts.Comments = comments + NewRenderer(opts).EscapeHTMLCallouts(buf, code) + if buf.String() != out { + t.Errorf("comments=%q\ngot:\n%s\nwant:\n%s", comments, buf.String(), out) + } + } +} diff --git a/html/renderer.go b/html/renderer.go index b3ee3922..60ff7f78 100644 --- a/html/renderer.go +++ b/html/renderer.go @@ -886,8 +886,9 @@ func (r *Renderer) EscapeHTMLCallouts(w io.Writer, d []byte) { Parse: for i := 0; i < ld; i++ { for _, comment := range r.Opts.Comments { + // try every configured marker, not just the first if !bytes.HasPrefix(d[i:], comment) { - break + continue } lc := len(comment)