Skip to content

Commit 210204c

Browse files
committed
fix(submit): track fence markers to prevent mismatched close
The fenced code block state machine treated `````` and `~~~` as interchangeable openers/closers. Now each code block tracks which marker opened it and only closes on the same marker, preventing a `~~~` inside a `````` block (or vice versa) from prematurely ending code block mode. Also adds explanatory comment to `submit_internal_test.go` for why it uses `package cmd` instead of `package cmd_test`.
1 parent 562c0e6 commit 210204c

2 files changed

Lines changed: 43 additions & 14 deletions

File tree

cmd/submit.go

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -570,24 +570,37 @@ var htmlTagRe = regexp.MustCompile(`</?[a-zA-Z][a-zA-Z0-9]*[\s/>]`)
570570
// before checking for HTML. Otherwise `<token>` in code would trigger a false positive.
571571
var inlineCodeRe = regexp.MustCompile("`[^`]+`")
572572

573+
// fenceMarker returns the fence prefix ("```" or "~~~") if the line opens or
574+
// closes a fenced code block, or "" otherwise.
575+
func fenceMarker(trimmedLine string) string {
576+
if strings.HasPrefix(trimmedLine, "```") {
577+
return "```"
578+
}
579+
if strings.HasPrefix(trimmedLine, "~~~") {
580+
return "~~~"
581+
}
582+
return ""
583+
}
584+
573585
// containsHTMLOutsideCode scans the text for HTML tags that appear in prose,
574586
// ignoring content inside fenced code blocks, indented code blocks, and inline
575587
// code spans. Returns true if HTML is found in any prose line.
576588
func containsHTMLOutsideCode(text string) bool {
577589
lines := strings.Split(text, "\n")
578-
inFencedCodeBlock := false
590+
var openFence string // tracks the opening fence marker ("```" or "~~~"), empty when outside
579591

580592
for _, line := range lines {
581593
trimmed := strings.TrimRight(line, " \t")
594+
marker := fenceMarker(trimmed)
582595

583-
// Track fenced code blocks (``` or ~~~)
584-
if !inFencedCodeBlock && (strings.HasPrefix(trimmed, "```") || strings.HasPrefix(trimmed, "~~~")) {
585-
inFencedCodeBlock = true
596+
// Track fenced code blocks — only the matching marker can close a block
597+
if openFence == "" && marker != "" {
598+
openFence = marker
586599
continue
587600
}
588-
if inFencedCodeBlock {
589-
if strings.HasPrefix(trimmed, "```") || strings.HasPrefix(trimmed, "~~~") {
590-
inFencedCodeBlock = false
601+
if openFence != "" {
602+
if marker == openFence {
603+
openFence = ""
591604
}
592605
continue
593606
}
@@ -620,7 +633,7 @@ func unwrapParagraphs(text string) string {
620633
lines := strings.Split(text, "\n")
621634
var result []string
622635
var paragraph []string
623-
inFencedCodeBlock := false
636+
var openFence string // tracks the opening fence marker ("```" or "~~~"), empty when outside
624637

625638
flushParagraph := func() {
626639
if len(paragraph) > 0 {
@@ -631,18 +644,19 @@ func unwrapParagraphs(text string) string {
631644

632645
for _, line := range lines {
633646
trimmed := strings.TrimRight(line, " \t")
647+
marker := fenceMarker(trimmed)
634648

635-
// Track fenced code blocks (``` or ~~~)
636-
if !inFencedCodeBlock && (strings.HasPrefix(trimmed, "```") || strings.HasPrefix(trimmed, "~~~")) {
649+
// Track fenced code blocks — only the matching marker can close a block
650+
if openFence == "" && marker != "" {
637651
flushParagraph()
638652
result = append(result, line)
639-
inFencedCodeBlock = true
653+
openFence = marker
640654
continue
641655
}
642-
if inFencedCodeBlock {
656+
if openFence != "" {
643657
result = append(result, line)
644-
if strings.HasPrefix(trimmed, "```") || strings.HasPrefix(trimmed, "~~~") {
645-
inFencedCodeBlock = false
658+
if marker == openFence {
659+
openFence = ""
646660
}
647661
continue
648662
}

cmd/submit_internal_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
// cmd/submit_internal_test.go
2+
//
3+
// This file uses package cmd (not cmd_test) to unit-test unexported helpers
4+
// like unwrapParagraphs, isBlockElement, etc. that are pure functions with no
5+
// dependency on command wiring. The external test files (package cmd_test)
6+
// cover command-level integration behavior.
27
package cmd
38

49
import (
@@ -118,6 +123,16 @@ func TestUnwrapParagraphs(t *testing.T) {
118123
in: "Use the <details> tag.\n\n```html\n<div>code</div>\n```",
119124
want: "Use the <details> tag.\n\n```html\n<div>code</div>\n```",
120125
},
126+
{
127+
name: "mismatched fence markers do not close each other",
128+
in: "Text before.\n\n```\n~~~\nstill in code\n```\n\nParagraph that is\nhard-wrapped.",
129+
want: "Text before.\n\n```\n~~~\nstill in code\n```\n\nParagraph that is hard-wrapped.",
130+
},
131+
{
132+
name: "tilde fence with backticks inside",
133+
in: "Text.\n\n~~~\n```\nnested marker\n~~~\n\nWrapped line\ncontinues here.",
134+
want: "Text.\n\n~~~\n```\nnested marker\n~~~\n\nWrapped line continues here.",
135+
},
121136
}
122137

123138
for _, tt := range tests {

0 commit comments

Comments
 (0)