blob: 6d4d1159a649bba4f3802c1dd57772f81600031b [file] [log] [blame]
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +10001// Copyright 2013 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 godoc is a work-in-progress (2013-07-17) package to
6// begin splitting up the godoc binary into multiple pieces.
7//
8// This package comment will evolve over time as this package splits
9// into smaller pieces.
David Symonds24257c82014-12-09 15:00:58 +110010package godoc // import "golang.org/x/tools/godoc"
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +100011
12import (
Brad Fitzpatrick32950ab2018-07-17 21:23:18 +000013 "bufio"
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +100014 "bytes"
15 "fmt"
16 "go/ast"
17 "go/doc"
18 "go/format"
19 "go/printer"
20 "go/token"
Alan Donovan80c4f062014-03-14 18:58:22 -040021 htmltemplate "html/template"
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +100022 "io"
23 "log"
24 "os"
25 pathpkg "path"
26 "regexp"
Brad Garciaff7cfaf2013-12-04 10:37:01 -050027 "strconv"
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +100028 "strings"
29 "text/template"
30 "time"
31 "unicode"
32 "unicode/utf8"
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +100033)
34
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +100035// Fake relative package path for built-ins. Documentation for all globals
Dmitri Shuralyov93949562019-02-18 13:49:49 -050036// (not just exported ones) will be shown for packages in this directory,
37// and there will be no association of consts, vars, and factory functions
38// with types (see issue 6645).
Brad Fitzpatrick705bb7f2013-07-19 10:27:53 +100039const builtinPkgPath = "builtin"
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +100040
41// FuncMap defines template functions used in godoc templates.
42//
43// Convention: template function names ending in "_html" or "_url" produce
44// HTML- or URL-escaped strings; all other function results may
45// require explicit escaping in the template.
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +100046func (p *Presentation) FuncMap() template.FuncMap {
47 p.initFuncMapOnce.Do(p.initFuncMap)
48 return p.funcMap
49}
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +100050
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +100051func (p *Presentation) TemplateFuncs() template.FuncMap {
52 p.initFuncMapOnce.Do(p.initFuncMap)
53 return p.templateFuncs
54}
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +100055
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +100056func (p *Presentation) initFuncMap() {
57 if p.Corpus == nil {
58 panic("nil Presentation.Corpus")
59 }
60 p.templateFuncs = template.FuncMap{
61 "code": p.code,
62 }
63 p.funcMap = template.FuncMap{
64 // various helpers
65 "filename": filenameFunc,
66 "repeat": strings.Repeat,
Devon H. O'Dell57f659e2017-12-22 17:30:39 -080067 "since": p.Corpus.pkgAPIInfo.sinceVersionFunc,
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +100068
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +100069 // access to FileInfos (directory listings)
70 "fileInfoName": fileInfoNameFunc,
71 "fileInfoTime": fileInfoTimeFunc,
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +100072
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +100073 // access to search result information
74 "infoKind_html": infoKind_htmlFunc,
75 "infoLine": p.infoLineFunc,
76 "infoSnippet_html": p.infoSnippet_htmlFunc,
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +100077
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +100078 // formatting of AST nodes
79 "node": p.nodeFunc,
80 "node_html": p.node_htmlFunc,
81 "comment_html": comment_htmlFunc,
Robert Griesemer42a4cd32014-04-07 12:54:28 -070082 "sanitize": sanitizeFunc,
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +100083
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +100084 // support for URL attributes
Sina Siadat5128de72016-09-16 17:12:50 +043085 "pkgLink": pkgLinkFunc,
86 "srcLink": srcLinkFunc,
87 "posLink_url": newPosLink_urlFunc(srcPosLinkFunc),
88 "docLink": docLinkFunc,
89 "queryLink": queryLinkFunc,
90 "srcBreadcrumb": srcBreadcrumbFunc,
91 "srcToPkgLink": srcToPkgLinkFunc,
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +100092
93 // formatting of Examples
94 "example_html": p.example_htmlFunc,
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +100095 "example_name": p.example_nameFunc,
96 "example_suffix": p.example_suffixFunc,
97
Alan Donovan80c4f062014-03-14 18:58:22 -040098 // formatting of analysis information
99 "callgraph_html": p.callgraph_htmlFunc,
100 "implements_html": p.implements_htmlFunc,
101 "methodset_html": p.methodset_htmlFunc,
102
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +1000103 // formatting of Notes
104 "noteTitle": noteTitle,
Matthew Holtf6712832015-04-17 17:09:18 -0600105
106 // Number operation
107 "multiply": multiply,
Ted Kornish396c1762017-01-18 10:52:36 -0800108
109 // formatting of PageInfoMode query string
110 "modeQueryString": modeQueryString,
Agniva De Sarker16d1af82018-01-28 19:53:55 +0530111
112 // check whether to display third party section or not
113 "hasThirdParty": hasThirdParty,
Agniva De Sarker3e7aa9e2018-08-11 13:33:17 +0530114
115 // get the no. of columns to split the toc in search page
116 "tocColCount": tocColCount,
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +1000117 }
Brad Garciad9d5e512013-11-01 13:20:32 -0400118 if p.URLForSrc != nil {
119 p.funcMap["srcLink"] = p.URLForSrc
120 }
121 if p.URLForSrcPos != nil {
122 p.funcMap["posLink_url"] = newPosLink_urlFunc(p.URLForSrcPos)
123 }
Brad Garciaff7cfaf2013-12-04 10:37:01 -0500124 if p.URLForSrcQuery != nil {
125 p.funcMap["queryLink"] = p.URLForSrcQuery
126 }
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000127}
128
Matthew Holtf6712832015-04-17 17:09:18 -0600129func multiply(a, b int) int { return a * b }
130
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000131func filenameFunc(path string) string {
132 _, localname := pathpkg.Split(path)
133 return localname
134}
135
136func fileInfoNameFunc(fi os.FileInfo) string {
137 name := fi.Name()
138 if fi.IsDir() {
139 name += "/"
140 }
141 return name
142}
143
144func fileInfoTimeFunc(fi os.FileInfo) string {
145 if t := fi.ModTime(); t.Unix() != 0 {
146 return t.Local().String()
147 }
148 return "" // don't return epoch if time is obviously not set
149}
150
151// The strings in infoKinds must be properly html-escaped.
152var infoKinds = [nKinds]string{
153 PackageClause: "package clause",
154 ImportDecl: "import decl",
155 ConstDecl: "const decl",
156 TypeDecl: "type decl",
157 VarDecl: "var decl",
158 FuncDecl: "func decl",
159 MethodDecl: "method decl",
160 Use: "use",
161}
162
163func infoKind_htmlFunc(info SpotInfo) string {
164 return infoKinds[info.Kind()] // infoKind entries are html-escaped
165}
166
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +1000167func (p *Presentation) infoLineFunc(info SpotInfo) int {
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000168 line := info.Lori()
169 if info.IsIndex() {
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +1000170 index, _ := p.Corpus.searchIndex.Get()
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000171 if index != nil {
172 line = index.(*Index).Snippet(line).Line
173 } else {
174 // no line information available because
175 // we don't have an index - this should
176 // never happen; be conservative and don't
177 // crash
178 line = 0
179 }
180 }
181 return line
182}
183
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +1000184func (p *Presentation) infoSnippet_htmlFunc(info SpotInfo) string {
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000185 if info.IsIndex() {
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +1000186 index, _ := p.Corpus.searchIndex.Get()
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000187 // Snippet.Text was HTML-escaped when it was generated
188 return index.(*Index).Snippet(info.Lori()).Text
189 }
190 return `<span class="alert">no snippet text available</span>`
191}
192
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +1000193func (p *Presentation) nodeFunc(info *PageInfo, node interface{}) string {
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000194 var buf bytes.Buffer
Brad Fitzpatrick32950ab2018-07-17 21:23:18 +0000195 p.writeNode(&buf, info, info.FSet, node)
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000196 return buf.String()
197}
198
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +1000199func (p *Presentation) node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string {
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000200 var buf1 bytes.Buffer
Brad Fitzpatrick32950ab2018-07-17 21:23:18 +0000201 p.writeNode(&buf1, info, info.FSet, node)
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000202
203 var buf2 bytes.Buffer
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +1000204 if n, _ := node.(ast.Node); n != nil && linkify && p.DeclLinks {
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000205 LinkifyText(&buf2, buf1.Bytes(), n)
Brad Fitzpatrick0f65b312016-11-29 23:53:36 +0000206 if st, name := isStructTypeDecl(n); st != nil {
207 addStructFieldIDAttributes(&buf2, name, st)
208 }
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000209 } else {
210 FormatText(&buf2, buf1.Bytes(), -1, true, "", nil)
211 }
212
213 return buf2.String()
214}
215
Brad Fitzpatrick0f65b312016-11-29 23:53:36 +0000216// isStructTypeDecl checks whether n is a struct declaration.
217// It either returns a non-nil StructType and its name, or zero values.
218func isStructTypeDecl(n ast.Node) (st *ast.StructType, name string) {
219 gd, ok := n.(*ast.GenDecl)
220 if !ok || gd.Tok != token.TYPE {
221 return nil, ""
222 }
223 if gd.Lparen > 0 {
224 // Parenthesized type. Who does that, anyway?
225 // TODO: Reportedly gri does. Fix this to handle that too.
226 return nil, ""
227 }
228 if len(gd.Specs) != 1 {
229 return nil, ""
230 }
231 ts, ok := gd.Specs[0].(*ast.TypeSpec)
232 if !ok {
233 return nil, ""
234 }
235 st, ok = ts.Type.(*ast.StructType)
236 if !ok {
237 return nil, ""
238 }
239 return st, ts.Name.Name
240}
241
242// addStructFieldIDAttributes modifies the contents of buf such that
243// all struct fields of the named struct have <span id='name.Field'>
244// in them, so people can link to /#Struct.Field.
245func addStructFieldIDAttributes(buf *bytes.Buffer, name string, st *ast.StructType) {
246 if st.Fields == nil {
247 return
248 }
Brad Fitzpatrick61efd712017-01-20 18:35:00 +0000249 // needsLink is a set of identifiers that still need to be
250 // linked, where value == key, to avoid an allocation in func
251 // linkedField.
252 needsLink := make(map[string]string)
253
Brad Fitzpatrick0f65b312016-11-29 23:53:36 +0000254 for _, f := range st.Fields.List {
255 if len(f.Names) == 0 {
256 continue
257 }
258 fieldName := f.Names[0].Name
Brad Fitzpatrick61efd712017-01-20 18:35:00 +0000259 needsLink[fieldName] = fieldName
Brad Fitzpatricke5f9a3d2016-11-30 23:49:04 +0000260 }
Brad Fitzpatrick61efd712017-01-20 18:35:00 +0000261 var newBuf bytes.Buffer
262 foreachLine(buf.Bytes(), func(line []byte) {
263 if fieldName := linkedField(line, needsLink); fieldName != "" {
264 fmt.Fprintf(&newBuf, `<span id="%s.%s"></span>`, name, fieldName)
265 delete(needsLink, fieldName)
266 }
267 newBuf.Write(line)
268 })
269 buf.Reset()
270 buf.Write(newBuf.Bytes())
Brad Fitzpatricke5f9a3d2016-11-30 23:49:04 +0000271}
272
273// foreachLine calls fn for each line of in, where a line includes
274// the trailing "\n", except on the last line, if it doesn't exist.
275func foreachLine(in []byte, fn func(line []byte)) {
276 for len(in) > 0 {
277 nl := bytes.IndexByte(in, '\n')
278 if nl == -1 {
279 fn(in)
280 return
281 }
282 fn(in[:nl+1])
283 in = in[nl+1:]
284 }
285}
286
287// commentPrefix is the line prefix for comments after they've been HTMLified.
288var commentPrefix = []byte(`<span class="comment">// `)
289
Brad Fitzpatrick61efd712017-01-20 18:35:00 +0000290// linkedField determines whether the given line starts with an
Anthony Fokdc59f202019-11-19 13:32:09 -0700291// identifier in the provided ids map (mapping from identifier to the
Brad Fitzpatrick61efd712017-01-20 18:35:00 +0000292// same identifier). The line can start with either an identifier or
293// an identifier in a comment. If one matches, it returns the
294// identifier that matched. Otherwise it returns the empty string.
295func linkedField(line []byte, ids map[string]string) string {
Brad Fitzpatricke5f9a3d2016-11-30 23:49:04 +0000296 line = bytes.TrimSpace(line)
297
298 // For fields with a doc string of the
299 // conventional form, we put the new span into
300 // the comment instead of the field.
301 // The "conventional" form is a complete sentence
302 // per https://golang.org/s/style#comment-sentences like:
303 //
304 // // Foo is an optional Fooer to foo the foos.
305 // Foo Fooer
306 //
307 // In this case, we want the #StructName.Foo
308 // link to make the browser go to the comment
309 // line "Foo is an optional Fooer" instead of
310 // the "Foo Fooer" line, which could otherwise
311 // obscure the docs above the browser's "fold".
312 //
313 // TODO: do this better, so it works for all
314 // comments, including unconventional ones.
Rebecca Stambler207d3de2019-11-20 22:43:00 -0500315 line = bytes.TrimPrefix(line, commentPrefix)
Brad Fitzpatrick61efd712017-01-20 18:35:00 +0000316 id := scanIdentifier(line)
317 if len(id) == 0 {
318 // No leading identifier. Avoid map lookup for
319 // somewhat common case.
320 return ""
321 }
322 return ids[string(id)]
323}
324
325// scanIdentifier scans a valid Go identifier off the front of v and
326// either returns a subslice of v if there's a valid identifier, or
327// returns a zero-length slice.
328func scanIdentifier(v []byte) []byte {
329 var n int // number of leading bytes of v belonging to an identifier
330 for {
331 r, width := utf8.DecodeRune(v[n:])
332 if !(isLetter(r) || n > 0 && isDigit(r)) {
333 break
334 }
335 n += width
336 }
337 return v[:n]
338}
339
340func isLetter(ch rune) bool {
341 return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch)
342}
343
344func isDigit(ch rune) bool {
345 return '0' <= ch && ch <= '9' || ch >= utf8.RuneSelf && unicode.IsDigit(ch)
Brad Fitzpatricke5f9a3d2016-11-30 23:49:04 +0000346}
Brad Fitzpatrick0f65b312016-11-29 23:53:36 +0000347
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000348func comment_htmlFunc(comment string) string {
349 var buf bytes.Buffer
350 // TODO(gri) Provide list of words (e.g. function parameters)
351 // to be emphasized by ToHTML.
352 doc.ToHTML(&buf, comment, nil) // does html-escaping
353 return buf.String()
354}
355
Robert Griesemer42a4cd32014-04-07 12:54:28 -0700356// sanitizeFunc sanitizes the argument src by replacing newlines with
357// blanks, removing extra blanks, and by removing trailing whitespace
358// and commas before closing parentheses.
359func sanitizeFunc(src string) string {
360 buf := make([]byte, len(src))
361 j := 0 // buf index
362 comma := -1 // comma index if >= 0
363 for i := 0; i < len(src); i++ {
364 ch := src[i]
365 switch ch {
366 case '\t', '\n', ' ':
367 // ignore whitespace at the beginning, after a blank, or after opening parentheses
368 if j == 0 {
369 continue
370 }
371 if p := buf[j-1]; p == ' ' || p == '(' || p == '{' || p == '[' {
372 continue
373 }
374 // replace all whitespace with blanks
375 ch = ' '
376 case ',':
377 comma = j
378 case ')', '}', ']':
379 // remove any trailing comma
380 if comma >= 0 {
381 j = comma
382 }
383 // remove any trailing whitespace
384 if j > 0 && buf[j-1] == ' ' {
385 j--
386 }
387 default:
388 comma = -1
389 }
390 buf[j] = ch
391 j++
392 }
393 // remove trailing blank, if any
394 if j > 0 && buf[j-1] == ' ' {
395 j--
396 }
397 return string(buf[:j])
398}
399
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000400type PageInfo struct {
Andrew Bonventre4e70a1b2017-08-02 16:37:59 -0400401 Dirname string // directory containing the package
402 Err error // error or nil
403 GoogleCN bool // page is being served from golang.google.cn
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000404
Ted Kornish396c1762017-01-18 10:52:36 -0800405 Mode PageInfoMode // display metadata from query string
406
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000407 // package info
Brad Garcia2ca2bfc2014-01-30 06:28:19 -0500408 FSet *token.FileSet // nil if no package documentation
409 PDoc *doc.Package // nil if no package documentation
410 Examples []*doc.Example // nil if no example code
411 Notes map[string][]*doc.Note // nil if no package Notes
412 PAst map[string]*ast.File // nil if no AST with package exports
413 IsMain bool // true for package main
414 IsFiltered bool // true if results were filtered
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000415
Alan Donovan80c4f062014-03-14 18:58:22 -0400416 // analysis info
417 TypeInfoIndex map[string]int // index of JSON datum for type T (if -analysis=type)
418 AnalysisData htmltemplate.JS // array of TypeInfoJSON values
419 CallGraph htmltemplate.JS // array of PCGNodeJSON values (if -analysis=pointer)
420 CallGraphIndex map[string]int // maps func name to index in CallGraph
421
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000422 // directory info
423 Dirs *DirList // nil if no directory information
424 DirTime time.Time // directory time stamp
425 DirFlat bool // if set, show directory in a flat (non-indented) manner
426}
427
428func (info *PageInfo) IsEmpty() bool {
429 return info.Err != nil || info.PAst == nil && info.PDoc == nil && info.Dirs == nil
430}
431
432func pkgLinkFunc(path string) string {
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000433 // because of the irregular mapping under goroot
434 // we need to correct certain relative paths
Brad Garcia936715c2014-06-27 10:25:57 -0400435 path = strings.TrimPrefix(path, "/")
436 path = strings.TrimPrefix(path, "src/")
437 path = strings.TrimPrefix(path, "pkg/")
438 return "pkg/" + path
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000439}
440
Andrew Bonventrea237aba2017-08-03 18:01:02 -0400441// srcToPkgLinkFunc builds an <a> tag linking to the package
442// documentation of relpath.
Sina Siadat5128de72016-09-16 17:12:50 +0430443func srcToPkgLinkFunc(relpath string) string {
444 relpath = pkgLinkFunc(relpath)
Andrew Bonventrea237aba2017-08-03 18:01:02 -0400445 relpath = pathpkg.Dir(relpath)
446 if relpath == "pkg" {
Sina Siadat5128de72016-09-16 17:12:50 +0430447 return `<a href="/pkg">Index</a>`
448 }
Sina Siadat5128de72016-09-16 17:12:50 +0430449 return fmt.Sprintf(`<a href="/%s">%s</a>`, relpath, relpath[len("pkg/"):])
450}
451
452// srcBreadcrumbFun converts each segment of relpath to a HTML <a>.
453// Each segment links to its corresponding src directories.
454func srcBreadcrumbFunc(relpath string) string {
455 segments := strings.Split(relpath, "/")
456 var buf bytes.Buffer
457 var selectedSegment string
458 var selectedIndex int
459
460 if strings.HasSuffix(relpath, "/") {
461 // relpath is a directory ending with a "/".
462 // Selected segment is the segment before the last slash.
463 selectedIndex = len(segments) - 2
464 selectedSegment = segments[selectedIndex] + "/"
465 } else {
466 selectedIndex = len(segments) - 1
467 selectedSegment = segments[selectedIndex]
468 }
469
470 for i := range segments[:selectedIndex] {
471 buf.WriteString(fmt.Sprintf(`<a href="/%s">%s</a>/`,
472 strings.Join(segments[:i+1], "/"),
473 segments[i],
474 ))
475 }
476
477 buf.WriteString(`<span class="text-muted">`)
478 buf.WriteString(selectedSegment)
479 buf.WriteString(`</span>`)
480 return buf.String()
481}
482
Brad Garciad9d5e512013-11-01 13:20:32 -0400483func newPosLink_urlFunc(srcPosLinkFunc func(s string, line, low, high int) string) func(info *PageInfo, n interface{}) string {
484 // n must be an ast.Node or a *doc.Note
485 return func(info *PageInfo, n interface{}) string {
486 var pos, end token.Pos
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000487
Brad Garciad9d5e512013-11-01 13:20:32 -0400488 switch n := n.(type) {
489 case ast.Node:
490 pos = n.Pos()
491 end = n.End()
492 case *doc.Note:
493 pos = n.Pos
494 end = n.End
495 default:
496 panic(fmt.Sprintf("wrong type for posLink_url template formatter: %T", n))
497 }
498
499 var relpath string
500 var line int
501 var low, high int // selection offset range
502
503 if pos.IsValid() {
504 p := info.FSet.Position(pos)
505 relpath = p.Filename
506 line = p.Line
507 low = p.Offset
508 }
509 if end.IsValid() {
510 high = info.FSet.Position(end).Offset
511 }
512
513 return srcPosLinkFunc(relpath, line, low, high)
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000514 }
Brad Garciad9d5e512013-11-01 13:20:32 -0400515}
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000516
Brad Garciad9d5e512013-11-01 13:20:32 -0400517func srcPosLinkFunc(s string, line, low, high int) string {
Brad Garcia61bd98e2014-02-21 19:51:51 -0500518 s = srcLinkFunc(s)
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000519 var buf bytes.Buffer
Brad Garciad9d5e512013-11-01 13:20:32 -0400520 template.HTMLEscape(&buf, []byte(s))
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000521 // selection ranges are of form "s=low:high"
522 if low < high {
523 fmt.Fprintf(&buf, "?s=%d:%d", low, high) // no need for URL escaping
524 // if we have a selection, position the page
525 // such that the selection is a bit below the top
526 line -= 10
527 if line < 1 {
528 line = 1
529 }
530 }
531 // line id's in html-printed source are of the
532 // form "L%d" where %d stands for the line number
533 if line > 0 {
534 fmt.Fprintf(&buf, "#L%d", line) // no need for URL escaping
535 }
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000536 return buf.String()
537}
538
539func srcLinkFunc(s string) string {
Brad Garcia61bd98e2014-02-21 19:51:51 -0500540 s = pathpkg.Clean("/" + s)
Alan Donovan6c93dbf2014-09-10 09:02:54 -0400541 if !strings.HasPrefix(s, "/src/") {
542 s = "/src" + s
Brad Garcia61bd98e2014-02-21 19:51:51 -0500543 }
544 return s
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000545}
546
Brad Garciaff7cfaf2013-12-04 10:37:01 -0500547// queryLinkFunc returns a URL for a line in a source file with a highlighted
548// query term.
549// s is expected to be a path to a source file.
550// query is expected to be a string that has already been appropriately escaped
551// for use in a URL query.
552func queryLinkFunc(s, query string, line int) string {
553 url := pathpkg.Clean("/"+s) + "?h=" + query
554 if line > 0 {
555 url += "#L" + strconv.Itoa(line)
556 }
557 return url
558}
559
Brad Garciaf3faf8b2013-11-21 11:55:42 -0500560func docLinkFunc(s string, ident string) string {
Brad Garcia936715c2014-06-27 10:25:57 -0400561 return pathpkg.Clean("/pkg/"+s) + "/#" + ident
Brad Garciaf3faf8b2013-11-21 11:55:42 -0500562}
563
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +1000564func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string {
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000565 var buf bytes.Buffer
566 for _, eg := range info.Examples {
567 name := stripExampleSuffix(eg.Name)
568
569 if name != funcName {
570 continue
571 }
572
573 // print code
574 cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +1000575 code := p.node_htmlFunc(info, cnode, true)
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000576 out := eg.Output
577 wholeFile := true
578
579 // Additional formatting if this is a function body.
580 if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
581 wholeFile = false
582 // remove surrounding braces
583 code = code[1 : n-1]
584 // unindent
Jay Conrod00f7cd52017-02-07 17:34:43 -0500585 code = replaceLeadingIndentation(code, strings.Repeat(" ", p.TabWidth), "")
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000586 // remove output comment
587 if loc := exampleOutputRx.FindStringIndex(code); loc != nil {
588 code = strings.TrimSpace(code[:loc[0]])
589 }
590 }
591
592 // Write out the playground code in standard Go style
593 // (use tabs, no comment highlight, etc).
594 play := ""
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +1000595 if eg.Play != nil && p.ShowPlayground {
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000596 var buf bytes.Buffer
Mostyn Bramley-Moore9e9f7f62018-07-31 17:27:32 +0000597 eg.Play.Comments = filterOutBuildAnnotations(eg.Play.Comments)
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000598 if err := format.Node(&buf, info.FSet, eg.Play); err != nil {
599 log.Print(err)
600 } else {
601 play = buf.String()
602 }
603 }
604
605 // Drop output, as the output comment will appear in the code.
606 if wholeFile && play == "" {
607 out = ""
608 }
609
Brad Fitzpatrick66f0d6e2013-07-18 13:51:17 +1000610 if p.ExampleHTML == nil {
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000611 out = ""
612 return ""
613 }
614
Brad Fitzpatrick66f0d6e2013-07-18 13:51:17 +1000615 err := p.ExampleHTML.Execute(&buf, struct {
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000616 Name, Doc, Code, Play, Output string
Andrew Bonventre4e70a1b2017-08-02 16:37:59 -0400617 GoogleCN bool
618 }{eg.Name, eg.Doc, code, play, out, info.GoogleCN})
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000619 if err != nil {
620 log.Print(err)
621 }
622 }
623 return buf.String()
624}
625
Mostyn Bramley-Moore9e9f7f62018-07-31 17:27:32 +0000626func filterOutBuildAnnotations(cg []*ast.CommentGroup) []*ast.CommentGroup {
627 if len(cg) == 0 {
628 return cg
629 }
630
631 for i := range cg {
632 if !strings.HasPrefix(cg[i].Text(), "+build ") {
633 // Found the first non-build tag, return from here until the end
634 // of the slice.
635 return cg[i:]
636 }
637 }
638
639 // There weren't any non-build tags, return an empty slice.
640 return []*ast.CommentGroup{}
641}
642
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000643// example_nameFunc takes an example function name and returns its display
644// name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +1000645func (p *Presentation) example_nameFunc(s string) string {
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000646 name, suffix := splitExampleName(s)
647 // replace _ with . for method names
648 name = strings.Replace(name, "_", ".", 1)
649 // use "Package" if no name provided
650 if name == "" {
651 name = "Package"
652 }
653 return name + suffix
654}
655
656// example_suffixFunc takes an example function name and returns its suffix in
657// parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +1000658func (p *Presentation) example_suffixFunc(name string) string {
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000659 _, suffix := splitExampleName(name)
660 return suffix
661}
662
Alan Donovan80c4f062014-03-14 18:58:22 -0400663// implements_html returns the "> Implements" toggle for a package-level named type.
664// Its contents are populated from JSON data by client-side JS at load time.
665func (p *Presentation) implements_htmlFunc(info *PageInfo, typeName string) string {
666 if p.ImplementsHTML == nil {
667 return ""
668 }
669 index, ok := info.TypeInfoIndex[typeName]
670 if !ok {
671 return ""
672 }
673 var buf bytes.Buffer
674 err := p.ImplementsHTML.Execute(&buf, struct{ Index int }{index})
675 if err != nil {
676 log.Print(err)
677 }
678 return buf.String()
679}
680
681// methodset_html returns the "> Method set" toggle for a package-level named type.
682// Its contents are populated from JSON data by client-side JS at load time.
683func (p *Presentation) methodset_htmlFunc(info *PageInfo, typeName string) string {
684 if p.MethodSetHTML == nil {
685 return ""
686 }
687 index, ok := info.TypeInfoIndex[typeName]
688 if !ok {
689 return ""
690 }
691 var buf bytes.Buffer
692 err := p.MethodSetHTML.Execute(&buf, struct{ Index int }{index})
693 if err != nil {
694 log.Print(err)
695 }
696 return buf.String()
697}
698
699// callgraph_html returns the "> Call graph" toggle for a package-level func.
700// Its contents are populated from JSON data by client-side JS at load time.
701func (p *Presentation) callgraph_htmlFunc(info *PageInfo, recv, name string) string {
702 if p.CallGraphHTML == nil {
703 return ""
704 }
705 if recv != "" {
706 // Format must match (*ssa.Function).RelString().
707 name = fmt.Sprintf("(%s).%s", recv, name)
708 }
709 index, ok := info.CallGraphIndex[name]
710 if !ok {
711 return ""
712 }
713 var buf bytes.Buffer
714 err := p.CallGraphHTML.Execute(&buf, struct{ Index int }{index})
715 if err != nil {
716 log.Print(err)
717 }
718 return buf.String()
719}
720
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000721func noteTitle(note string) string {
722 return strings.Title(strings.ToLower(note))
723}
724
725func startsWithUppercase(s string) bool {
726 r, _ := utf8.DecodeRuneInString(s)
727 return unicode.IsUpper(r)
728}
729
Andrew Gerrand477d3b92016-04-14 09:31:55 +1000730var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*(unordered )?output:`)
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000731
732// stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name
733// while keeping uppercase Braz in Foo_Braz.
734func stripExampleSuffix(name string) string {
735 if i := strings.LastIndex(name, "_"); i != -1 {
736 if i < len(name)-1 && !startsWithUppercase(name[i+1:]) {
737 name = name[:i]
738 }
739 }
740 return name
741}
742
743func splitExampleName(s string) (name, suffix string) {
744 i := strings.LastIndex(s, "_")
745 if 0 <= i && i < len(s)-1 && !startsWithUppercase(s[i+1:]) {
746 name = s[:i]
747 suffix = " (" + strings.Title(s[i+1:]) + ")"
748 return
749 }
750 name = s
751 return
752}
753
Jay Conrod00f7cd52017-02-07 17:34:43 -0500754// replaceLeadingIndentation replaces oldIndent at the beginning of each line
755// with newIndent. This is used for formatting examples. Raw strings that
756// span multiple lines are handled specially: oldIndent is not removed (since
757// go/printer will not add any indentation there), but newIndent is added
758// (since we may still want leading indentation).
759func replaceLeadingIndentation(body, oldIndent, newIndent string) string {
760 // Handle indent at the beginning of the first line. After this, we handle
761 // indentation only after a newline.
762 var buf bytes.Buffer
763 if strings.HasPrefix(body, oldIndent) {
764 buf.WriteString(newIndent)
765 body = body[len(oldIndent):]
766 }
767
768 // Use a state machine to keep track of whether we're in a string or
769 // rune literal while we process the rest of the code.
770 const (
771 codeState = iota
772 runeState
773 interpretedStringState
774 rawStringState
775 )
776 searchChars := []string{
777 "'\"`\n", // codeState
778 `\'`, // runeState
779 `\"`, // interpretedStringState
780 "`\n", // rawStringState
781 // newlineState does not need to search
782 }
783 state := codeState
784 for {
785 i := strings.IndexAny(body, searchChars[state])
786 if i < 0 {
787 buf.WriteString(body)
788 break
789 }
790 c := body[i]
791 buf.WriteString(body[:i+1])
792 body = body[i+1:]
793 switch state {
794 case codeState:
795 switch c {
796 case '\'':
797 state = runeState
798 case '"':
799 state = interpretedStringState
800 case '`':
801 state = rawStringState
802 case '\n':
803 if strings.HasPrefix(body, oldIndent) {
804 buf.WriteString(newIndent)
805 body = body[len(oldIndent):]
806 }
807 }
808
809 case runeState:
810 switch c {
811 case '\\':
812 r, size := utf8.DecodeRuneInString(body)
813 buf.WriteRune(r)
814 body = body[size:]
815 case '\'':
816 state = codeState
817 }
818
819 case interpretedStringState:
820 switch c {
821 case '\\':
822 r, size := utf8.DecodeRuneInString(body)
823 buf.WriteRune(r)
824 body = body[size:]
825 case '"':
826 state = codeState
827 }
828
829 case rawStringState:
830 switch c {
831 case '`':
832 state = codeState
833 case '\n':
834 buf.WriteString(newIndent)
835 }
836 }
837 }
838 return buf.String()
839}
840
Brad Fitzpatrick32950ab2018-07-17 21:23:18 +0000841// writeNode writes the AST node x to w.
842//
843// The provided fset must be non-nil. The pageInfo is optional. If
844// present, the pageInfo is used to add comments to struct fields to
845// say which version of Go introduced them.
846func (p *Presentation) writeNode(w io.Writer, pageInfo *PageInfo, fset *token.FileSet, x interface{}) {
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000847 // convert trailing tabs into spaces using a tconv filter
848 // to ensure a good outcome in most browsers (there may still
849 // be tabs in comments and strings, but converting those into
850 // the right number of spaces is much harder)
851 //
852 // TODO(gri) rethink printer flags - perhaps tconv can be eliminated
853 // with an another printer mode (which is more efficiently
854 // implemented in the printer than here with another layer)
Brad Fitzpatrick32950ab2018-07-17 21:23:18 +0000855
856 var pkgName, structName string
857 var apiInfo pkgAPIVersions
858 if gd, ok := x.(*ast.GenDecl); ok && pageInfo != nil && pageInfo.PDoc != nil &&
859 p.Corpus != nil &&
860 gd.Tok == token.TYPE && len(gd.Specs) != 0 {
861 pkgName = pageInfo.PDoc.ImportPath
862 if ts, ok := gd.Specs[0].(*ast.TypeSpec); ok {
863 if _, ok := ts.Type.(*ast.StructType); ok {
864 structName = ts.Name.Name
865 }
866 }
867 apiInfo = p.Corpus.pkgAPIInfo[pkgName]
868 }
869
870 var out = w
871 var buf bytes.Buffer
872 if structName != "" {
873 out = &buf
874 }
875
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000876 mode := printer.TabIndent | printer.UseSpaces
Brad Fitzpatrick32950ab2018-07-17 21:23:18 +0000877 err := (&printer.Config{Mode: mode, Tabwidth: p.TabWidth}).Fprint(&tconv{p: p, output: out}, fset, x)
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000878 if err != nil {
879 log.Print(err)
880 }
Brad Fitzpatrick32950ab2018-07-17 21:23:18 +0000881
Ainar Garipovfeee8ac2019-09-11 09:14:36 +0300882 // Add comments to struct fields saying which Go version introduced them.
Brad Fitzpatrick32950ab2018-07-17 21:23:18 +0000883 if structName != "" {
884 fieldSince := apiInfo.fieldSince[structName]
885 typeSince := apiInfo.typeSince[structName]
886 // Add/rewrite comments on struct fields to note which Go version added them.
887 var buf2 bytes.Buffer
888 buf2.Grow(buf.Len() + len(" // Added in Go 1.n")*10)
889 bs := bufio.NewScanner(&buf)
890 for bs.Scan() {
891 line := bs.Bytes()
892 field := firstIdent(line)
893 var since string
894 if field != "" {
895 since = fieldSince[field]
896 if since != "" && since == typeSince {
897 // Don't highlight field versions if they were the
898 // same as the struct itself.
899 since = ""
900 }
901 }
902 if since == "" {
903 buf2.Write(line)
904 } else {
905 if bytes.Contains(line, slashSlash) {
906 line = bytes.TrimRight(line, " \t.")
907 buf2.Write(line)
908 buf2.WriteString("; added in Go ")
909 } else {
910 buf2.Write(line)
911 buf2.WriteString(" // Go ")
912 }
913 buf2.WriteString(since)
914 }
915 buf2.WriteByte('\n')
916 }
917 w.Write(buf2.Bytes())
918 }
Brad Fitzpatrickca3319f2013-07-17 17:17:12 +1000919}
920
Brad Fitzpatrick32950ab2018-07-17 21:23:18 +0000921var slashSlash = []byte("//")
922
Robert Griesemer42a4cd32014-04-07 12:54:28 -0700923// WriteNode writes x to w.
924// TODO(bgarcia) Is this method needed? It's just a wrapper for p.writeNode.
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +1000925func (p *Presentation) WriteNode(w io.Writer, fset *token.FileSet, x interface{}) {
Brad Fitzpatrick32950ab2018-07-17 21:23:18 +0000926 p.writeNode(w, nil, fset, x)
927}
928
929// firstIdent returns the first identifier in x.
930// This actually parses "identifiers" that begin with numbers too, but we
931// never feed it such input, so it's fine.
932func firstIdent(x []byte) string {
933 x = bytes.TrimSpace(x)
934 i := bytes.IndexFunc(x, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsNumber(r) })
935 if i == -1 {
936 return string(x)
937 }
938 return string(x[:i])
Brad Fitzpatrick5395cfe2013-07-18 13:14:09 +1000939}