github.com/bir3/gocompiler@v0.3.205/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 headingPrefix string 152 needDoc map[string]bool 153 } 154 155 // Comment returns the standard Go formatting of the Doc, 156 // without any comment markers. 157 func (p *Printer) Comment(d *Doc) []byte { 158 cp := &commentPrinter{Printer: p} 159 var out bytes.Buffer 160 for i, x := range d.Content { 161 if i > 0 && blankBefore(x) { 162 out.WriteString("\n") 163 } 164 cp.block(&out, x) 165 } 166 167 // Print one block containing all the link definitions that were used, 168 // and then a second block containing all the unused ones. 169 // This makes it easy to clean up the unused ones: gofmt and 170 // delete the final block. And it's a nice visual signal without 171 // affecting the way the comment formats for users. 172 for i := 0; i < 2; i++ { 173 used := i == 0 174 first := true 175 for _, def := range d.Links { 176 if def.Used == used { 177 if first { 178 out.WriteString("\n") 179 first = false 180 } 181 out.WriteString("[") 182 out.WriteString(def.Text) 183 out.WriteString("]: ") 184 out.WriteString(def.URL) 185 out.WriteString("\n") 186 } 187 } 188 } 189 190 return out.Bytes() 191 } 192 193 // blankBefore reports whether the block x requires a blank line before it. 194 // All blocks do, except for Lists that return false from x.BlankBefore(). 195 func blankBefore(x Block) bool { 196 if x, ok := x.(*List); ok { 197 return x.BlankBefore() 198 } 199 return true 200 } 201 202 // block prints the block x to out. 203 func (p *commentPrinter) block(out *bytes.Buffer, x Block) { 204 switch x := x.(type) { 205 default: 206 fmt.Fprintf(out, "?%T", x) 207 208 case *Paragraph: 209 p.text(out, "", x.Text) 210 out.WriteString("\n") 211 212 case *Heading: 213 out.WriteString("# ") 214 p.text(out, "", x.Text) 215 out.WriteString("\n") 216 217 case *Code: 218 md := x.Text 219 for md != "" { 220 var line string 221 line, md, _ = strings.Cut(md, "\n") 222 if line != "" { 223 out.WriteString("\t") 224 out.WriteString(line) 225 } 226 out.WriteString("\n") 227 } 228 229 case *List: 230 loose := x.BlankBetween() 231 for i, item := range x.Items { 232 if i > 0 && loose { 233 out.WriteString("\n") 234 } 235 out.WriteString(" ") 236 if item.Number == "" { 237 out.WriteString(" - ") 238 } else { 239 out.WriteString(item.Number) 240 out.WriteString(". ") 241 } 242 for i, blk := range item.Content { 243 const fourSpace = " " 244 if i > 0 { 245 out.WriteString("\n" + fourSpace) 246 } 247 p.text(out, fourSpace, blk.(*Paragraph).Text) 248 out.WriteString("\n") 249 } 250 } 251 } 252 } 253 254 // text prints the text sequence x to out. 255 func (p *commentPrinter) text(out *bytes.Buffer, indent string, x []Text) { 256 for _, t := range x { 257 switch t := t.(type) { 258 case Plain: 259 p.indent(out, indent, string(t)) 260 case Italic: 261 p.indent(out, indent, string(t)) 262 case *Link: 263 if t.Auto { 264 p.text(out, indent, t.Text) 265 } else { 266 out.WriteString("[") 267 p.text(out, indent, t.Text) 268 out.WriteString("]") 269 } 270 case *DocLink: 271 out.WriteString("[") 272 p.text(out, indent, t.Text) 273 out.WriteString("]") 274 } 275 } 276 } 277 278 // indent prints s to out, indenting with the indent string 279 // after each newline in s. 280 func (p *commentPrinter) indent(out *bytes.Buffer, indent, s string) { 281 for s != "" { 282 line, rest, ok := strings.Cut(s, "\n") 283 out.WriteString(line) 284 if ok { 285 out.WriteString("\n") 286 out.WriteString(indent) 287 } 288 s = rest 289 } 290 }