Repository: magefile/mage (2026-03-15T13:51:21Z)

Issue:
Bug: Backticks in magefile package comment breaks mage_output_file.go
**Bug Description**
I wrote this as a package comment in my magefile.go:

```
// This is a magefile, to be called with `go tool mage` (github.com/magefile/mage).
package main
```

**What did you do?**
I called `go tool mage`.

**What did you expect to happen?**
Successful run.

**What actually happened?**
mage_output_file.go didn't compile.

**Environment**
```
$ go tool mage --version
Mage Build Tool <not set>
Build Date: <not set>
Commit: <not set>
built with: go1.26.0
```
(latest as of today)

**Additional context**
The line that breaks in mage_output_file.go is:

```go
_fmt.Println(`This is a magefile, to be called with `go tool mage` (github.com/magefile/mage).
`)
```

The string needs escaping.


Discussion:
Oof yep.  I'll get on it. 



Patch:
diff --git a/parse/parse.go b/parse/parse.go
index a124937..2e9de79 100644
--- a/parse/parse.go
+++ b/parse/parse.go
@@ -51,8 +51,8 @@ type Function struct {
 	Receiver   string
 	IsError    bool
 	IsContext  bool
-	Synopsis   string
-	Comment    string
+	Synopsis   string // Synopsis is a one sentence description of the function, without its leading function name.
+	Comment    string // Comment is the full comment on the function, with newlines replaced by spaces and trimmed.
 	Args       []Arg
 }
 
@@ -504,7 +504,7 @@ func Package(path string, files []string, multiline bool) (*PkgInfo, error) {
 	if multiline {
 		pi.Description = strings.TrimSuffix(p.Doc, "\n")
 	} else {
-		pi.Description = toOneLine(p.Doc)
+		pi.Description = oneLineDoc(p.Doc)
 	}
 
 	setNamespaces(pi, fieldComments)
@@ -645,7 +645,7 @@ func funcFromDoc(f *doc.Func, importpath, funcname string, multiline bool, field
 	if multiline {
 		fn.Comment = strings.TrimSuffix(f.Doc, "\n")
 	} else {
-		fn.Comment = toOneLine(f.Doc)
+		fn.Comment = oneLineDoc(f.Doc)
 	}
 	fn.Synopsis = sanitizeSynopsis(f)
 	return fn, true
@@ -810,6 +810,7 @@ func checkDupeTargets(info *PkgInfo) (hasDupes bool, names map[string][]string)
 // sanitizeSynopsis sanitizes function Doc to create a summary.
 func sanitizeSynopsis(f *doc.Func) string {
 	synopsis := doc.Synopsis(f.Doc)
+	synopsis = sanitizeDocComment(synopsis)
 
 	// If the synopsis begins with the function name, remove it. This is done to
 	// not repeat the text.
@@ -817,8 +818,12 @@ func sanitizeSynopsis(f *doc.Func) string {
 	// clean	Clean removes the temporarily generated files
 	// To:
 	// clean 	removes the temporarily generated files
-	if syns := strings.Split(synopsis, " "); strings.EqualFold(f.Name, syns[0]) {
-		return strings.Join(syns[1:], " ")
+	start, rest, found := strings.Cut(synopsis, " ")
+	if !found {
+		return synopsis
+	}
+	if strings.EqualFold(f.Name, start) {
+		return rest
 	}
 
 	return synopsis
@@ -1111,8 +1116,18 @@ func funcType(ft *ast.FuncType, fieldComments map[*ast.Field]string) (*Function,
 	return f, nil
 }
 
-func toOneLine(s string) string {
-	return strings.TrimSpace(strings.ReplaceAll(s, "\n", " "))
+// sanitizeDocComment sanitizes a doc comment by replacing characters that would screw up formatting
+// in the output file.
+func sanitizeDocComment(s string) string {
+	s = strings.ReplaceAll(s, "`", "'")
+	return s
+}
+
+// oneLineDoc converts a doc comment to a single line, and sanitizes it for output.
+func oneLineDoc(s string) string {
+	s = strings.ReplaceAll(s, "\n", " ")
+	s = strings.TrimSpace(s)
+	return sanitizeDocComment(s)
 }
 
 // hasComment reports whether any file in the package contains a comment


Test patch:
diff --git a/parse/parse_test.go b/parse/parse_test.go
index 0d3567d..91bdc9f 100644
--- a/parse/parse_test.go
+++ b/parse/parse_test.go
@@ -57,6 +57,12 @@ func TestParse(t *testing.T) {
 			Receiver: "Build",
 			IsError:  false,
 		},
+		{
+			Name:     "WithBackticks",
+			IsError:  false,
+			Comment:  "WithBackticks has a synopsis that includes 'backticks' which were a problem once.",
+			Synopsis: "has a synopsis that includes 'backticks' which were a problem once.",
+		},
 	}
 
 	if info.DefaultFunc == nil {
@@ -92,11 +98,14 @@ func TestParse(t *testing.T) {
 	for _, fn := range expected {
 		found := false
 		for _, infoFn := range info.Funcs {
-			if reflect.DeepEqual(fn, *infoFn) {
-				found = true
+			if fn.Name == infoFn.Name && fn.Receiver == infoFn.Receiver {
+				if reflect.DeepEqual(fn, *infoFn) {
+					found = true
+					break
+				}
+				t.Errorf("expected:\n%#v\n\nto equal:\n%#v", fn, *infoFn)
 				break
 			}
-			t.Logf("%#v", infoFn)
 		}
 		if !found {
 			t.Fatalf("expected:\n%#v\n\nto be in:\n%#v", fn, info.Funcs)
diff --git a/parse/testdata/func.go b/parse/testdata/func.go
index ffc81fd..56992fb 100644
--- a/parse/testdata/func.go
+++ b/parse/testdata/func.go
@@ -13,3 +13,6 @@ func ReturnsNilError() error {
 }
 
 func nonexported() {}
+
+// WithBackticks has a synopsis that includes `backticks` which were a problem once.
+func WithBackticks() {}