Fix inline code rendering and error handling consistence

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-04-06 16:01:51 +04:00
parent 23eaa4ce24
commit ad326224b3
2 changed files with 66 additions and 20 deletions

View File

@@ -247,12 +247,26 @@ func (r *mdRenderer) renderText(n Node) error {
return nil return nil
} }
// maxConsecutiveBackticks returns the length of the longest run of '`' in s.
// Inline code fences must be longer than this value (CommonMark).
func maxConsecutiveBackticks(s string) int {
max, cur := 0, 0
for i := 0; i < len(s); i++ {
if s[i] == '`' {
cur++
if cur > max {
max = cur
}
} else {
cur = 0
}
}
return max
}
func (r *mdRenderer) renderCodeText(n Node) error { func (r *mdRenderer) renderCodeText(n Node) error {
text := *n.Text text := *n.Text
backtick := "`" fence := strings.Repeat("`", maxConsecutiveBackticks(text)+1)
if strings.Contains(text, "`") {
backtick = "``"
}
var otherMarks []Mark var otherMarks []Mark
for _, m := range n.Marks { for _, m := range n.Marks {
@@ -267,15 +281,15 @@ func (r *mdRenderer) renderCodeText(n Node) error {
} }
} }
r.buf.WriteString(backtick) r.buf.WriteString(fence)
if backtick == "``" { if len(fence) > 1 {
r.buf.WriteByte(' ') r.buf.WriteByte(' ')
} }
r.buf.WriteString(text) r.buf.WriteString(text)
if backtick == "``" { if len(fence) > 1 {
r.buf.WriteByte(' ') r.buf.WriteByte(' ')
} }
r.buf.WriteString(backtick) r.buf.WriteString(fence)
for i := len(otherMarks) - 1; i >= 0; i-- { for i := len(otherMarks) - 1; i >= 0; i-- {
if err := r.closeMark(otherMarks[i]); err != nil { if err := r.closeMark(otherMarks[i]); err != nil {
@@ -334,11 +348,11 @@ func (r *mdRenderer) closeMark(m Mark) error {
} }
func (r *mdRenderer) renderBulletList(n Node) error { func (r *mdRenderer) renderBulletList(n Node) error {
tight := isTightList(n) tight, err := listTightness(n)
if err != nil {
return fmt.Errorf("cannot render bullet list: %w", err)
}
for i, item := range n.Content { for i, item := range n.Content {
if item.Type != NodeListItem {
continue
}
if i > 0 && !tight { if i > 0 && !tight {
r.ensurePrefix() r.ensurePrefix()
r.newLine() r.newLine()
@@ -369,12 +383,12 @@ func (r *mdRenderer) renderOrderedList(n Node) error {
if err != nil { if err != nil {
return fmt.Errorf("cannot render ordered list node: %w", err) return fmt.Errorf("cannot render ordered list node: %w", err)
} }
tight := isTightList(n) tight, err := listTightness(n)
if err != nil {
return fmt.Errorf("cannot render ordered list: %w", err)
}
start := max(attrs.Start, 1) start := max(attrs.Start, 1)
for i, item := range n.Content { for i, item := range n.Content {
if item.Type != NodeListItem {
continue
}
if i > 0 && !tight { if i > 0 && !tight {
r.ensurePrefix() r.ensurePrefix()
r.newLine() r.newLine()
@@ -461,16 +475,23 @@ func (r *mdRenderer) renderCellInline(cell Node) error {
return nil return nil
} }
func isTightList(n Node) bool { // listTightness returns true when the list is tight (no blank lines between items).
// Every direct child of n must be a listItem; otherwise listTightness returns an error.
func listTightness(n Node) (tight bool, err error) {
tight = true
for _, item := range n.Content { for _, item := range n.Content {
if item.Type != NodeListItem { if item.Type != NodeListItem {
continue return false, fmt.Errorf(
"invalid child type %q (expected %q)",
item.Type,
NodeListItem,
)
} }
if len(item.Content) != 1 { if len(item.Content) != 1 {
return false tight = false
} }
} }
return true return tight, nil
} }
func collectText(nodes []Node) string { func collectText(nodes []Node) string {

View File

@@ -199,6 +199,31 @@ func TestRenderMarkdown_InlineCodeWithBacktick(t *testing.T) {
assert.Equal(t, "`` a ` b ``\n", got) assert.Equal(t, "`` a ` b ``\n", got)
} }
func TestRenderMarkdown_InlineCodeWithDoubleBacktickRun(t *testing.T) {
t.Parallel()
// Two consecutive backticks in content need a 3+ backtick fence.
raw := `{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","marks":[{"type":"code"}],"text":"` + "``" + `x"}]}]}`
var n Node
require.NoError(t, json.Unmarshal([]byte(raw), &n))
got, err := RenderMarkdown(n)
require.NoError(t, err)
assert.Equal(t, "``` ``x ```\n", got)
}
func TestRenderMarkdown_InlineCodeWithTripleBacktickRun(t *testing.T) {
t.Parallel()
raw := `{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","marks":[{"type":"code"}],"text":"` + "```" + `"}]}]}`
var n Node
require.NoError(t, json.Unmarshal([]byte(raw), &n))
got, err := RenderMarkdown(n)
require.NoError(t, err)
assert.Equal(t, "```` ``` ````\n", got)
}
func TestRenderMarkdown_Link(t *testing.T) { func TestRenderMarkdown_Link(t *testing.T) {
t.Parallel() t.Parallel()