Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / godoc / godoc.go
1 // 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.
10 package godoc // import "golang.org/x/tools/godoc"
11
12 import (
13         "bufio"
14         "bytes"
15         "fmt"
16         "go/ast"
17         "go/doc"
18         "go/format"
19         "go/printer"
20         "go/token"
21         htmltemplate "html/template"
22         "io"
23         "log"
24         "os"
25         pathpkg "path"
26         "regexp"
27         "strconv"
28         "strings"
29         "text/template"
30         "time"
31         "unicode"
32         "unicode/utf8"
33 )
34
35 // Fake relative package path for built-ins. Documentation for all globals
36 // (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).
39 const builtinPkgPath = "builtin"
40
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.
46 func (p *Presentation) FuncMap() template.FuncMap {
47         p.initFuncMapOnce.Do(p.initFuncMap)
48         return p.funcMap
49 }
50
51 func (p *Presentation) TemplateFuncs() template.FuncMap {
52         p.initFuncMapOnce.Do(p.initFuncMap)
53         return p.templateFuncs
54 }
55
56 func (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,
67                 "since":    p.Corpus.pkgAPIInfo.sinceVersionFunc,
68
69                 // access to FileInfos (directory listings)
70                 "fileInfoName": fileInfoNameFunc,
71                 "fileInfoTime": fileInfoTimeFunc,
72
73                 // access to search result information
74                 "infoKind_html":    infoKind_htmlFunc,
75                 "infoLine":         p.infoLineFunc,
76                 "infoSnippet_html": p.infoSnippet_htmlFunc,
77
78                 // formatting of AST nodes
79                 "node":         p.nodeFunc,
80                 "node_html":    p.node_htmlFunc,
81                 "comment_html": comment_htmlFunc,
82                 "sanitize":     sanitizeFunc,
83
84                 // support for URL attributes
85                 "pkgLink":       pkgLinkFunc,
86                 "srcLink":       srcLinkFunc,
87                 "posLink_url":   newPosLink_urlFunc(srcPosLinkFunc),
88                 "docLink":       docLinkFunc,
89                 "queryLink":     queryLinkFunc,
90                 "srcBreadcrumb": srcBreadcrumbFunc,
91                 "srcToPkgLink":  srcToPkgLinkFunc,
92
93                 // formatting of Examples
94                 "example_html":   p.example_htmlFunc,
95                 "example_name":   p.example_nameFunc,
96                 "example_suffix": p.example_suffixFunc,
97
98                 // formatting of analysis information
99                 "callgraph_html":  p.callgraph_htmlFunc,
100                 "implements_html": p.implements_htmlFunc,
101                 "methodset_html":  p.methodset_htmlFunc,
102
103                 // formatting of Notes
104                 "noteTitle": noteTitle,
105
106                 // Number operation
107                 "multiply": multiply,
108
109                 // formatting of PageInfoMode query string
110                 "modeQueryString": modeQueryString,
111
112                 // check whether to display third party section or not
113                 "hasThirdParty": hasThirdParty,
114
115                 // get the no. of columns to split the toc in search page
116                 "tocColCount": tocColCount,
117         }
118         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         }
124         if p.URLForSrcQuery != nil {
125                 p.funcMap["queryLink"] = p.URLForSrcQuery
126         }
127 }
128
129 func multiply(a, b int) int { return a * b }
130
131 func filenameFunc(path string) string {
132         _, localname := pathpkg.Split(path)
133         return localname
134 }
135
136 func fileInfoNameFunc(fi os.FileInfo) string {
137         name := fi.Name()
138         if fi.IsDir() {
139                 name += "/"
140         }
141         return name
142 }
143
144 func 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.
152 var 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
163 func infoKind_htmlFunc(info SpotInfo) string {
164         return infoKinds[info.Kind()] // infoKind entries are html-escaped
165 }
166
167 func (p *Presentation) infoLineFunc(info SpotInfo) int {
168         line := info.Lori()
169         if info.IsIndex() {
170                 index, _ := p.Corpus.searchIndex.Get()
171                 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
184 func (p *Presentation) infoSnippet_htmlFunc(info SpotInfo) string {
185         if info.IsIndex() {
186                 index, _ := p.Corpus.searchIndex.Get()
187                 // 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
193 func (p *Presentation) nodeFunc(info *PageInfo, node interface{}) string {
194         var buf bytes.Buffer
195         p.writeNode(&buf, info, info.FSet, node)
196         return buf.String()
197 }
198
199 func (p *Presentation) node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string {
200         var buf1 bytes.Buffer
201         p.writeNode(&buf1, info, info.FSet, node)
202
203         var buf2 bytes.Buffer
204         if n, _ := node.(ast.Node); n != nil && linkify && p.DeclLinks {
205                 LinkifyText(&buf2, buf1.Bytes(), n)
206                 if st, name := isStructTypeDecl(n); st != nil {
207                         addStructFieldIDAttributes(&buf2, name, st)
208                 }
209         } else {
210                 FormatText(&buf2, buf1.Bytes(), -1, true, "", nil)
211         }
212
213         return buf2.String()
214 }
215
216 // isStructTypeDecl checks whether n is a struct declaration.
217 // It either returns a non-nil StructType and its name, or zero values.
218 func 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.
245 func addStructFieldIDAttributes(buf *bytes.Buffer, name string, st *ast.StructType) {
246         if st.Fields == nil {
247                 return
248         }
249         // 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
254         for _, f := range st.Fields.List {
255                 if len(f.Names) == 0 {
256                         continue
257                 }
258                 fieldName := f.Names[0].Name
259                 needsLink[fieldName] = fieldName
260         }
261         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())
271 }
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.
275 func 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.
288 var commentPrefix = []byte(`<span class="comment">// `)
289
290 // linkedField determines whether the given line starts with an
291 // identifier in the provided ids map (mapping from identifier to the
292 // 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.
295 func linkedField(line []byte, ids map[string]string) string {
296         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.
315         line = bytes.TrimPrefix(line, commentPrefix)
316         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.
328 func 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
340 func isLetter(ch rune) bool {
341         return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch)
342 }
343
344 func isDigit(ch rune) bool {
345         return '0' <= ch && ch <= '9' || ch >= utf8.RuneSelf && unicode.IsDigit(ch)
346 }
347
348 func 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
356 // 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.
359 func 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
400 type PageInfo struct {
401         Dirname  string // directory containing the package
402         Err      error  // error or nil
403         GoogleCN bool   // page is being served from golang.google.cn
404
405         Mode PageInfoMode // display metadata from query string
406
407         // package info
408         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
415
416         // 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
422         // 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
428 func (info *PageInfo) IsEmpty() bool {
429         return info.Err != nil || info.PAst == nil && info.PDoc == nil && info.Dirs == nil
430 }
431
432 func pkgLinkFunc(path string) string {
433         // because of the irregular mapping under goroot
434         // we need to correct certain relative paths
435         path = strings.TrimPrefix(path, "/")
436         path = strings.TrimPrefix(path, "src/")
437         path = strings.TrimPrefix(path, "pkg/")
438         return "pkg/" + path
439 }
440
441 // srcToPkgLinkFunc builds an <a> tag linking to the package
442 // documentation of relpath.
443 func srcToPkgLinkFunc(relpath string) string {
444         relpath = pkgLinkFunc(relpath)
445         relpath = pathpkg.Dir(relpath)
446         if relpath == "pkg" {
447                 return `<a href="/pkg">Index</a>`
448         }
449         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.
454 func 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
483 func 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
487
488                 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)
514         }
515 }
516
517 func srcPosLinkFunc(s string, line, low, high int) string {
518         s = srcLinkFunc(s)
519         var buf bytes.Buffer
520         template.HTMLEscape(&buf, []byte(s))
521         // 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         }
536         return buf.String()
537 }
538
539 func srcLinkFunc(s string) string {
540         s = pathpkg.Clean("/" + s)
541         if !strings.HasPrefix(s, "/src/") {
542                 s = "/src" + s
543         }
544         return s
545 }
546
547 // 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.
552 func 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
560 func docLinkFunc(s string, ident string) string {
561         return pathpkg.Clean("/pkg/"+s) + "/#" + ident
562 }
563
564 func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string {
565         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}
575                 code := p.node_htmlFunc(info, cnode, true)
576                 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
585                         code = replaceLeadingIndentation(code, strings.Repeat(" ", p.TabWidth), "")
586                         // 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 := ""
595                 if eg.Play != nil && p.ShowPlayground {
596                         var buf bytes.Buffer
597                         eg.Play.Comments = filterOutBuildAnnotations(eg.Play.Comments)
598                         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
610                 if p.ExampleHTML == nil {
611                         out = ""
612                         return ""
613                 }
614
615                 err := p.ExampleHTML.Execute(&buf, struct {
616                         Name, Doc, Code, Play, Output string
617                         GoogleCN                      bool
618                 }{eg.Name, eg.Doc, code, play, out, info.GoogleCN})
619                 if err != nil {
620                         log.Print(err)
621                 }
622         }
623         return buf.String()
624 }
625
626 func 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
643 // example_nameFunc takes an example function name and returns its display
644 // name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
645 func (p *Presentation) example_nameFunc(s string) string {
646         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)".
658 func (p *Presentation) example_suffixFunc(name string) string {
659         _, suffix := splitExampleName(name)
660         return suffix
661 }
662
663 // 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.
665 func (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.
683 func (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.
701 func (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
721 func noteTitle(note string) string {
722         return strings.Title(strings.ToLower(note))
723 }
724
725 func startsWithUppercase(s string) bool {
726         r, _ := utf8.DecodeRuneInString(s)
727         return unicode.IsUpper(r)
728 }
729
730 var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*(unordered )?output:`)
731
732 // stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name
733 // while keeping uppercase Braz in Foo_Braz.
734 func 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
743 func 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
754 // 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).
759 func 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
841 // 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.
846 func (p *Presentation) writeNode(w io.Writer, pageInfo *PageInfo, fset *token.FileSet, x interface{}) {
847         // 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)
855
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
876         mode := printer.TabIndent | printer.UseSpaces
877         err := (&printer.Config{Mode: mode, Tabwidth: p.TabWidth}).Fprint(&tconv{p: p, output: out}, fset, x)
878         if err != nil {
879                 log.Print(err)
880         }
881
882         // Add comments to struct fields saying which Go version introduced them.
883         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         }
919 }
920
921 var slashSlash = []byte("//")
922
923 // WriteNode writes x to w.
924 // TODO(bgarcia) Is this method needed? It's just a wrapper for p.writeNode.
925 func (p *Presentation) WriteNode(w io.Writer, fset *token.FileSet, x interface{}) {
926         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.
932 func 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])
939 }