github.com/bir3/gocompiler@v0.9.2202/src/go/doc/comment/print.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package comment 6 7 import ( 8 "bytes" 9 "fmt" 10 "strings" 11 ) 12 13 // A Printer is a doc comment printer. 14 // The fields in the struct can be filled in before calling 15 // any of the printing methods 16 // in order to customize the details of the printing process. 17 type Printer struct { 18 // HeadingLevel is the nesting level used for 19 // HTML and Markdown headings. 20 // If HeadingLevel is zero, it defaults to level 3, 21 // meaning to use <h3> and ###. 22 HeadingLevel int 23 24 // HeadingID is a function that computes the heading ID 25 // (anchor tag) to use for the heading h when generating 26 // HTML and Markdown. If HeadingID returns an empty string, 27 // then the heading ID is omitted. 28 // If HeadingID is nil, h.DefaultID is used. 29 HeadingID func(h *Heading) string 30 31 // DocLinkURL is a function that computes the URL for the given DocLink. 32 // If DocLinkURL is nil, then link.DefaultURL(p.DocLinkBaseURL) is used. 33 DocLinkURL func(link *DocLink) string 34 35 // DocLinkBaseURL is used when DocLinkURL is nil, 36 // passed to [DocLink.DefaultURL] to construct a DocLink's URL. 37 // See that method's documentation for details. 38 DocLinkBaseURL string 39 40 // TextPrefix is a prefix to print at the start of every line 41 // when generating text output using the Text method. 42 TextPrefix string 43 44 // TextCodePrefix is the prefix to print at the start of each 45 // preformatted (code block) line when generating text output, 46 // instead of (not in addition to) TextPrefix. 47 // If TextCodePrefix is the empty string, it defaults to TextPrefix+"\t". 48 TextCodePrefix string 49 50 // TextWidth is the maximum width text line to generate, 51 // measured in Unicode code points, 52 // excluding TextPrefix and the newline character. 53 // If TextWidth is zero, it defaults to 80 minus the number of code points in TextPrefix. 54 // If TextWidth is negative, there is no limit. 55 TextWidth int 56 } 57 58 func (p *Printer) headingLevel() int { 59 if p.HeadingLevel <= 0 { 60 return 3 61 } 62 return p.HeadingLevel 63 } 64 65 func (p *Printer) headingID(h *Heading) string { 66 if p.HeadingID == nil { 67 return h.DefaultID() 68 } 69 return p.HeadingID(h) 70 } 71 72 func (p *Printer) docLinkURL(link *DocLink) string { 73 if p.DocLinkURL != nil { 74 return p.DocLinkURL(link) 75 } 76 return link.DefaultURL(p.DocLinkBaseURL) 77 } 78 79 // DefaultURL constructs and returns the documentation URL for l, 80 // using baseURL as a prefix for links to other packages. 81 // 82 // The possible forms returned by DefaultURL are: 83 // - baseURL/ImportPath, for a link to another package 84 // - baseURL/ImportPath#Name, for a link to a const, func, type, or var in another package 85 // - baseURL/ImportPath#Recv.Name, for a link to a method in another package 86 // - #Name, for a link to a const, func, type, or var in this package 87 // - #Recv.Name, for a link to a method in this package 88 // 89 // If baseURL ends in a trailing slash, then DefaultURL inserts 90 // a slash between ImportPath and # in the anchored forms. 91 // For example, here are some baseURL values and URLs they can generate: 92 // 93 // "/pkg/" → "/pkg/math/#Sqrt" 94 // "/pkg" → "/pkg/math#Sqrt" 95 // "/" → "/math/#Sqrt" 96 // "" → "/math#Sqrt" 97 func (l *DocLink) DefaultURL(baseURL string) string { 98 if l.ImportPath != "" { 99 slash := "" 100 if strings.HasSuffix(baseURL, "/") { 101 slash = "/" 102 } else { 103 baseURL += "/" 104 } 105 switch { 106 case l.Name == "": 107 return baseURL + l.ImportPath + slash 108 case l.Recv != "": 109 return baseURL + l.ImportPath + slash + "#" + l.Recv + "." + l.Name 110 default: 111 return baseURL + l.ImportPath + slash + "#" + l.Name 112 } 113 } 114 if l.Recv != "" { 115 return "#" + l.Recv + "." + l.Name 116 } 117 return "#" + l.Name 118 } 119 120 // DefaultID returns the default anchor ID for the heading h. 121 // 122 // The default anchor ID is constructed by converting every 123 // rune that is not alphanumeric ASCII to an underscore 124 // and then adding the prefix “hdr-”. 125 // For example, if the heading text is “Go Doc Comments”, 126 // the default ID is “hdr-Go_Doc_Comments”. 127 func (h *Heading) DefaultID() string { 128 // Note: The “hdr-” prefix is important to avoid DOM clobbering attacks. 129 // See https://pkg.go.dev/github.com/google/safehtml#Identifier. 130 var out strings.Builder 131 var p textPrinter 132 p.oneLongLine(&out, h.Text) 133 s := strings.TrimSpace(out.String()) 134 if s == "" { 135 return "" 136 } 137 out.Reset() 138 out.WriteString("hdr-") 139 for _, r := range s { 140 if r < 0x80 && isIdentASCII(byte(r)) { 141 out.WriteByte(byte(r)) 142 } else { 143 out.WriteByte('_') 144 } 145 } 146 return out.String() 147 } 148 149 type commentPrinter struct { 150 *Printer 151 } 152 153 // Comment returns the standard Go formatting of the [Doc], 154 // without any comment markers. 155 func (p *Printer) Comment(d *Doc) []byte { 156 cp := &commentPrinter{Printer: p} 157 var out bytes.Buffer 158 for i, x := range d.Content { 159 if i > 0 && blankBefore(x) { 160 out.WriteString("\n") 161 } 162 cp.block(&out, x) 163 } 164 165 // Print one block containing all the link definitions that were used, 166 // and then a second block containing all the unused ones. 167 // This makes it easy to clean up the unused ones: gofmt and 168 // delete the final block. And it's a nice visual signal without 169 // affecting the way the comment formats for users. 170 for i := 0; i < 2; i++ { 171 used := i == 0 172 first := true 173 for _, def := range d.Links { 174 if def.Used == used { 175 if first { 176 out.WriteString("\n") 177 first = false 178 } 179 out.WriteString("[") 180 out.WriteString(def.Text) 181 out.WriteString("]: ") 182 out.WriteString(def.URL) 183 out.WriteString("\n") 184 } 185 } 186 } 187 188 return out.Bytes() 189 } 190 191 // blankBefore reports whether the block x requires a blank line before it. 192 // All blocks do, except for Lists that return false from x.BlankBefore(). 193 func blankBefore(x Block) bool { 194 if x, ok := x.(*List); ok { 195 return x.BlankBefore() 196 } 197 return true 198 } 199 200 // block prints the block x to out. 201 func (p *commentPrinter) block(out *bytes.Buffer, x Block) { 202 switch x := x.(type) { 203 default: 204 fmt.Fprintf(out, "?%T", x) 205 206 case *Paragraph: 207 p.text(out, "", x.Text) 208 out.WriteString("\n") 209 210 case *Heading: 211 out.WriteString("# ") 212 p.text(out, "", x.Text) 213 out.WriteString("\n") 214 215 case *Code: 216 md := x.Text 217 for md != "" { 218 var line string 219 line, md, _ = strings.Cut(md, "\n") 220 if line != "" { 221 out.WriteString("\t") 222 out.WriteString(line) 223 } 224 out.WriteString("\n") 225 } 226 227 case *List: 228 loose := x.BlankBetween() 229 for i, item := range x.Items { 230 if i > 0 && loose { 231 out.WriteString("\n") 232 } 233 out.WriteString(" ") 234 if item.Number == "" { 235 out.WriteString(" - ") 236 } else { 237 out.WriteString(item.Number) 238 out.WriteString(". ") 239 } 240 for i, blk := range item.Content { 241 const fourSpace = " " 242 if i > 0 { 243 out.WriteString("\n" + fourSpace) 244 } 245 p.text(out, fourSpace, blk.(*Paragraph).Text) 246 out.WriteString("\n") 247 } 248 } 249 } 250 } 251 252 // text prints the text sequence x to out. 253 func (p *commentPrinter) text(out *bytes.Buffer, indent string, x []Text) { 254 for _, t := range x { 255 switch t := t.(type) { 256 case Plain: 257 p.indent(out, indent, string(t)) 258 case Italic: 259 p.indent(out, indent, string(t)) 260 case *Link: 261 if t.Auto { 262 p.text(out, indent, t.Text) 263 } else { 264 out.WriteString("[") 265 p.text(out, indent, t.Text) 266 out.WriteString("]") 267 } 268 case *DocLink: 269 out.WriteString("[") 270 p.text(out, indent, t.Text) 271 out.WriteString("]") 272 } 273 } 274 } 275 276 // indent prints s to out, indenting with the indent string 277 // after each newline in s. 278 func (p *commentPrinter) indent(out *bytes.Buffer, indent, s string) { 279 for s != "" { 280 line, rest, ok := strings.Cut(s, "\n") 281 out.WriteString(line) 282 if ok { 283 out.WriteString("\n") 284 out.WriteString(indent) 285 } 286 s = rest 287 } 288 }