GoPLS Viewer

Home|gopls/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.
10package godoc // import "golang.org/x/tools/godoc"
11
12import (
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).
39const 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.
46func (p *PresentationFuncMap() template.FuncMap {
47    p.initFuncMapOnce.Do(p.initFuncMap)
48    return p.funcMap
49}
50
51func (p *PresentationTemplateFuncs() template.FuncMap {
52    p.initFuncMapOnce.Do(p.initFuncMap)
53    return p.templateFuncs
54}
55
56func (p *PresentationinitFuncMap() {
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
129func multiply(ab intint { return a * b }
130
131func filenameFunc(path stringstring {
132    _localname := pathpkg.Split(path)
133    return localname
134}
135
136func fileInfoNameFunc(fi os.FileInfostring {
137    name := fi.Name()
138    if fi.IsDir() {
139        name += "/"
140    }
141    return name
142}
143
144func fileInfoTimeFunc(fi os.FileInfostring {
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 SpotInfostring {
164    return infoKinds[info.Kind()] // infoKind entries are html-escaped
165}
166
167func (p *PresentationinfoLineFunc(info SpotInfoint {
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
184func (p *PresentationinfoSnippet_htmlFunc(info SpotInfostring {
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
193func (p *PresentationnodeFunc(info *PageInfonode interface{}) string {
194    var buf bytes.Buffer
195    p.writeNode(&bufinfoinfo.FSetnode)
196    return buf.String()
197}
198
199func (p *Presentationnode_htmlFunc(info *PageInfonode interface{}, linkify boolstring {
200    var buf1 bytes.Buffer
201    p.writeNode(&buf1infoinfo.FSetnode)
202
203    var buf2 bytes.Buffer
204    if n_ := node.(ast.Node); n != nil && linkify && p.DeclLinks {
205        LinkifyText(&buf2buf1.Bytes(), n)
206        if stname := isStructTypeDecl(n); st != nil {
207            addStructFieldIDAttributes(&buf2namest)
208        }
209    } else {
210        FormatText(&buf2buf1.Bytes(), -1true""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.
218func isStructTypeDecl(n ast.Node) (st *ast.StructTypename string) {
219    gdok := 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    tsok := gd.Specs[0].(*ast.TypeSpec)
232    if !ok {
233        return nil""
234    }
235    stok = ts.Type.(*ast.StructType)
236    if !ok {
237        return nil""
238    }
239    return stts.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.Buffername stringst *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(lineneedsLink); fieldName != "" {
264            fmt.Fprintf(&newBuf`<span id="%s.%s"></span>`namefieldName)
265            delete(needsLinkfieldName)
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.
275func foreachLine(in []bytefn 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
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.
295func linkedField(line []byteids map[string]stringstring {
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(linecommentPrefix)
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.
328func scanIdentifier(v []byte) []byte {
329    var n int // number of leading bytes of v belonging to an identifier
330    for {
331        rwidth := 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 runebool {
341    return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch)
342}
343
344func isDigit(ch runebool {
345    return '0' <= ch && ch <= '9' || ch >= utf8.RuneSelf && unicode.IsDigit(ch)
346}
347
348func comment_htmlFunc(info *PageInfocomment stringstring {
349    var buf bytes.Buffer
350    // TODO(gri) Provide list of words (e.g. function parameters)
351    //           to be emphasized by ToHTML.
352
353    // godocToHTML is:
354    // - buf.Write(info.PDoc.HTML(comment)) on go1.19
355    // - go/doc.ToHTML(&buf, comment, nil) on other versions
356    godocToHTML(&bufinfo.PDoccomment)
357
358    return buf.String()
359}
360
361// sanitizeFunc sanitizes the argument src by replacing newlines with
362// blanks, removing extra blanks, and by removing trailing whitespace
363// and commas before closing parentheses.
364func sanitizeFunc(src stringstring {
365    buf := make([]bytelen(src))
366    j := 0      // buf index
367    comma := -1 // comma index if >= 0
368    for i := 0i < len(src); i++ {
369        ch := src[i]
370        switch ch {
371        case '\t''\n'' ':
372            // ignore whitespace at the beginning, after a blank, or after opening parentheses
373            if j == 0 {
374                continue
375            }
376            if p := buf[j-1]; p == ' ' || p == '(' || p == '{' || p == '[' {
377                continue
378            }
379            // replace all whitespace with blanks
380            ch = ' '
381        case ',':
382            comma = j
383        case ')''}'']':
384            // remove any trailing comma
385            if comma >= 0 {
386                j = comma
387            }
388            // remove any trailing whitespace
389            if j > 0 && buf[j-1] == ' ' {
390                j--
391            }
392        default:
393            comma = -1
394        }
395        buf[j] = ch
396        j++
397    }
398    // remove trailing blank, if any
399    if j > 0 && buf[j-1] == ' ' {
400        j--
401    }
402    return string(buf[:j])
403}
404
405type PageInfo struct {
406    Dirname string // directory containing the package
407    Err     error  // error or nil
408
409    Mode PageInfoMode // display metadata from query string
410
411    // package info
412    FSet       *token.FileSet         // nil if no package documentation
413    PDoc       *doc.Package           // nil if no package documentation
414    Examples   []*doc.Example         // nil if no example code
415    Notes      map[string][]*doc.Note // nil if no package Notes
416    PAst       map[string]*ast.File   // nil if no AST with package exports
417    IsMain     bool                   // true for package main
418    IsFiltered bool                   // true if results were filtered
419
420    // analysis info
421    TypeInfoIndex  map[string]int  // index of JSON datum for type T (if -analysis=type)
422    AnalysisData   htmltemplate.JS // array of TypeInfoJSON values
423    CallGraph      htmltemplate.JS // array of PCGNodeJSON values    (if -analysis=pointer)
424    CallGraphIndex map[string]int  // maps func name to index in CallGraph
425
426    // directory info
427    Dirs    *DirList  // nil if no directory information
428    DirTime time.Time // directory time stamp
429    DirFlat bool      // if set, show directory in a flat (non-indented) manner
430}
431
432func (info *PageInfoIsEmpty() bool {
433    return info.Err != nil || info.PAst == nil && info.PDoc == nil && info.Dirs == nil
434}
435
436func pkgLinkFunc(path stringstring {
437    // because of the irregular mapping under goroot
438    // we need to correct certain relative paths
439    path = strings.TrimPrefix(path"/")
440    path = strings.TrimPrefix(path"src/")
441    path = strings.TrimPrefix(path"pkg/")
442    return "pkg/" + path
443}
444
445// srcToPkgLinkFunc builds an <a> tag linking to the package
446// documentation of relpath.
447func srcToPkgLinkFunc(relpath stringstring {
448    relpath = pkgLinkFunc(relpath)
449    relpath = pathpkg.Dir(relpath)
450    if relpath == "pkg" {
451        return `<a href="/pkg">Index</a>`
452    }
453    return fmt.Sprintf(`<a href="/%s">%s</a>`relpathrelpath[len("pkg/"):])
454}
455
456// srcBreadcrumbFunc converts each segment of relpath to a HTML <a>.
457// Each segment links to its corresponding src directories.
458func srcBreadcrumbFunc(relpath stringstring {
459    segments := strings.Split(relpath"/")
460    var buf bytes.Buffer
461    var selectedSegment string
462    var selectedIndex int
463
464    if strings.HasSuffix(relpath"/") {
465        // relpath is a directory ending with a "/".
466        // Selected segment is the segment before the last slash.
467        selectedIndex = len(segments) - 2
468        selectedSegment = segments[selectedIndex] + "/"
469    } else {
470        selectedIndex = len(segments) - 1
471        selectedSegment = segments[selectedIndex]
472    }
473
474    for i := range segments[:selectedIndex] {
475        buf.WriteString(fmt.Sprintf(`<a href="/%s">%s</a>/`,
476            strings.Join(segments[:i+1], "/"),
477            segments[i],
478        ))
479    }
480
481    buf.WriteString(`<span class="text-muted">`)
482    buf.WriteString(selectedSegment)
483    buf.WriteString(`</span>`)
484    return buf.String()
485}
486
487func newPosLink_urlFunc(srcPosLinkFunc func(s stringlinelowhigh intstring) func(info *PageInfon interface{}) string {
488    // n must be an ast.Node or a *doc.Note
489    return func(info *PageInfon interface{}) string {
490        var posend token.Pos
491
492        switch n := n.(type) {
493        case ast.Node:
494            pos = n.Pos()
495            end = n.End()
496        case *doc.Note:
497            pos = n.Pos
498            end = n.End
499        default:
500            panic(fmt.Sprintf("wrong type for posLink_url template formatter: %T"n))
501        }
502
503        var relpath string
504        var line int
505        var lowhigh int // selection offset range
506
507        if pos.IsValid() {
508            p := info.FSet.Position(pos)
509            relpath = p.Filename
510            line = p.Line
511            low = p.Offset
512        }
513        if end.IsValid() {
514            high = info.FSet.Position(end).Offset
515        }
516
517        return srcPosLinkFunc(relpathlinelowhigh)
518    }
519}
520
521func srcPosLinkFunc(s stringlinelowhigh intstring {
522    s = srcLinkFunc(s)
523    var buf bytes.Buffer
524    template.HTMLEscape(&buf, []byte(s))
525    // selection ranges are of form "s=low:high"
526    if low < high {
527        fmt.Fprintf(&buf"?s=%d:%d"lowhigh// no need for URL escaping
528        // if we have a selection, position the page
529        // such that the selection is a bit below the top
530        line -= 10
531        if line < 1 {
532            line = 1
533        }
534    }
535    // line id's in html-printed source are of the
536    // form "L%d" where %d stands for the line number
537    if line > 0 {
538        fmt.Fprintf(&buf"#L%d"line// no need for URL escaping
539    }
540    return buf.String()
541}
542
543func srcLinkFunc(s stringstring {
544    s = pathpkg.Clean("/" + s)
545    if !strings.HasPrefix(s"/src/") {
546        s = "/src" + s
547    }
548    return s
549}
550
551// queryLinkFunc returns a URL for a line in a source file with a highlighted
552// query term.
553// s is expected to be a path to a source file.
554// query is expected to be a string that has already been appropriately escaped
555// for use in a URL query.
556func queryLinkFunc(squery stringline intstring {
557    url := pathpkg.Clean("/"+s) + "?h=" + query
558    if line > 0 {
559        url += "#L" + strconv.Itoa(line)
560    }
561    return url
562}
563
564func docLinkFunc(s stringident stringstring {
565    return pathpkg.Clean("/pkg/"+s) + "/#" + ident
566}
567
568func (p *Presentationexample_htmlFunc(info *PageInfofuncName stringstring {
569    var buf bytes.Buffer
570    for _eg := range info.Examples {
571        name := stripExampleSuffix(eg.Name)
572
573        if name != funcName {
574            continue
575        }
576
577        // print code
578        cnode := &printer.CommentedNode{Nodeeg.CodeCommentseg.Comments}
579        code := p.node_htmlFunc(infocnodetrue)
580        out := eg.Output
581        wholeFile := true
582
583        // Additional formatting if this is a function body.
584        if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
585            wholeFile = false
586            // remove surrounding braces
587            code = code[1 : n-1]
588            // unindent
589            code = replaceLeadingIndentation(codestrings.Repeat(" "p.TabWidth), "")
590            // remove output comment
591            if loc := exampleOutputRx.FindStringIndex(code); loc != nil {
592                code = strings.TrimSpace(code[:loc[0]])
593            }
594        }
595
596        // Write out the playground code in standard Go style
597        // (use tabs, no comment highlight, etc).
598        play := ""
599        if eg.Play != nil && p.ShowPlayground {
600            var buf bytes.Buffer
601            eg.Play.Comments = filterOutBuildAnnotations(eg.Play.Comments)
602            if err := format.Node(&bufinfo.FSeteg.Play); err != nil {
603                log.Print(err)
604            } else {
605                play = buf.String()
606            }
607        }
608
609        // Drop output, as the output comment will appear in the code.
610        if wholeFile && play == "" {
611            out = ""
612        }
613
614        if p.ExampleHTML == nil {
615            out = ""
616            return ""
617        }
618
619        err := p.ExampleHTML.Execute(&buf, struct {
620            NameDocCodePlayOutput string
621        }{eg.Nameeg.Doccodeplayout})
622        if err != nil {
623            log.Print(err)
624        }
625    }
626    return buf.String()
627}
628
629func filterOutBuildAnnotations(cg []*ast.CommentGroup) []*ast.CommentGroup {
630    if len(cg) == 0 {
631        return cg
632    }
633
634    for i := range cg {
635        if !strings.HasPrefix(cg[i].Text(), "+build ") {
636            // Found the first non-build tag, return from here until the end
637            // of the slice.
638            return cg[i:]
639        }
640    }
641
642    // There weren't any non-build tags, return an empty slice.
643    return []*ast.CommentGroup{}
644}
645
646// example_nameFunc takes an example function name and returns its display
647// name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
648func (p *Presentationexample_nameFunc(s stringstring {
649    namesuffix := splitExampleName(s)
650    // replace _ with . for method names
651    name = strings.Replace(name"_""."1)
652    // use "Package" if no name provided
653    if name == "" {
654        name = "Package"
655    }
656    return name + suffix
657}
658
659// example_suffixFunc takes an example function name and returns its suffix in
660// parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
661func (p *Presentationexample_suffixFunc(name stringstring {
662    _suffix := splitExampleName(name)
663    return suffix
664}
665
666// implements_htmlFunc returns the "> Implements" toggle for a package-level named type.
667// Its contents are populated from JSON data by client-side JS at load time.
668func (p *Presentationimplements_htmlFunc(info *PageInfotypeName stringstring {
669    if p.ImplementsHTML == nil {
670        return ""
671    }
672    indexok := info.TypeInfoIndex[typeName]
673    if !ok {
674        return ""
675    }
676    var buf bytes.Buffer
677    err := p.ImplementsHTML.Execute(&buf, struct{ Index int }{index})
678    if err != nil {
679        log.Print(err)
680    }
681    return buf.String()
682}
683
684// methodset_htmlFunc returns the "> Method set" toggle for a package-level named type.
685// Its contents are populated from JSON data by client-side JS at load time.
686func (p *Presentationmethodset_htmlFunc(info *PageInfotypeName stringstring {
687    if p.MethodSetHTML == nil {
688        return ""
689    }
690    indexok := info.TypeInfoIndex[typeName]
691    if !ok {
692        return ""
693    }
694    var buf bytes.Buffer
695    err := p.MethodSetHTML.Execute(&buf, struct{ Index int }{index})
696    if err != nil {
697        log.Print(err)
698    }
699    return buf.String()
700}
701
702// callgraph_htmlFunc returns the "> Call graph" toggle for a package-level func.
703// Its contents are populated from JSON data by client-side JS at load time.
704func (p *Presentationcallgraph_htmlFunc(info *PageInforecvname stringstring {
705    if p.CallGraphHTML == nil {
706        return ""
707    }
708    if recv != "" {
709        // Format must match (*ssa.Function).RelString().
710        name = fmt.Sprintf("(%s).%s"recvname)
711    }
712    indexok := info.CallGraphIndex[name]
713    if !ok {
714        return ""
715    }
716    var buf bytes.Buffer
717    err := p.CallGraphHTML.Execute(&buf, struct{ Index int }{index})
718    if err != nil {
719        log.Print(err)
720    }
721    return buf.String()
722}
723
724func noteTitle(note stringstring {
725    return strings.Title(strings.ToLower(note))
726}
727
728func startsWithUppercase(s stringbool {
729    r_ := utf8.DecodeRuneInString(s)
730    return unicode.IsUpper(r)
731}
732
733var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*(unordered )?output:`)
734
735// stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name
736// while keeping uppercase Braz in Foo_Braz.
737func stripExampleSuffix(name stringstring {
738    if i := strings.LastIndex(name"_"); i != -1 {
739        if i < len(name)-1 && !startsWithUppercase(name[i+1:]) {
740            name = name[:i]
741        }
742    }
743    return name
744}
745
746func splitExampleName(s string) (namesuffix string) {
747    i := strings.LastIndex(s"_")
748    if 0 <= i && i < len(s)-1 && !startsWithUppercase(s[i+1:]) {
749        name = s[:i]
750        suffix = " (" + strings.Title(s[i+1:]) + ")"
751        return
752    }
753    name = s
754    return
755}
756
757// replaceLeadingIndentation replaces oldIndent at the beginning of each line
758// with newIndent. This is used for formatting examples. Raw strings that
759// span multiple lines are handled specially: oldIndent is not removed (since
760// go/printer will not add any indentation there), but newIndent is added
761// (since we may still want leading indentation).
762func replaceLeadingIndentation(bodyoldIndentnewIndent stringstring {
763    // Handle indent at the beginning of the first line. After this, we handle
764    // indentation only after a newline.
765    var buf bytes.Buffer
766    if strings.HasPrefix(bodyoldIndent) {
767        buf.WriteString(newIndent)
768        body = body[len(oldIndent):]
769    }
770
771    // Use a state machine to keep track of whether we're in a string or
772    // rune literal while we process the rest of the code.
773    const (
774        codeState = iota
775        runeState
776        interpretedStringState
777        rawStringState
778    )
779    searchChars := []string{
780        "'\"`\n"// codeState
781        `\'`,     // runeState
782        `\"`,     // interpretedStringState
783        "`\n",    // rawStringState
784        // newlineState does not need to search
785    }
786    state := codeState
787    for {
788        i := strings.IndexAny(bodysearchChars[state])
789        if i < 0 {
790            buf.WriteString(body)
791            break
792        }
793        c := body[i]
794        buf.WriteString(body[:i+1])
795        body = body[i+1:]
796        switch state {
797        case codeState:
798            switch c {
799            case '\'':
800                state = runeState
801            case '"':
802                state = interpretedStringState
803            case '`':
804                state = rawStringState
805            case '\n':
806                if strings.HasPrefix(bodyoldIndent) {
807                    buf.WriteString(newIndent)
808                    body = body[len(oldIndent):]
809                }
810            }
811
812        case runeState:
813            switch c {
814            case '\\':
815                rsize := utf8.DecodeRuneInString(body)
816                buf.WriteRune(r)
817                body = body[size:]
818            case '\'':
819                state = codeState
820            }
821
822        case interpretedStringState:
823            switch c {
824            case '\\':
825                rsize := utf8.DecodeRuneInString(body)
826                buf.WriteRune(r)
827                body = body[size:]
828            case '"':
829                state = codeState
830            }
831
832        case rawStringState:
833            switch c {
834            case '`':
835                state = codeState
836            case '\n':
837                buf.WriteString(newIndent)
838            }
839        }
840    }
841    return buf.String()
842}
843
844// writeNode writes the AST node x to w.
845//
846// The provided fset must be non-nil. The pageInfo is optional. If
847// present, the pageInfo is used to add comments to struct fields to
848// say which version of Go introduced them.
849func (p *PresentationwriteNode(w io.WriterpageInfo *PageInfofset *token.FileSetx interface{}) {
850    // convert trailing tabs into spaces using a tconv filter
851    // to ensure a good outcome in most browsers (there may still
852    // be tabs in comments and strings, but converting those into
853    // the right number of spaces is much harder)
854    //
855    // TODO(gri) rethink printer flags - perhaps tconv can be eliminated
856    //           with an another printer mode (which is more efficiently
857    //           implemented in the printer than here with another layer)
858
859    var pkgNamestructName string
860    var apiInfo pkgAPIVersions
861    if gdok := x.(*ast.GenDecl); ok && pageInfo != nil && pageInfo.PDoc != nil &&
862        p.Corpus != nil &&
863        gd.Tok == token.TYPE && len(gd.Specs) != 0 {
864        pkgName = pageInfo.PDoc.ImportPath
865        if tsok := gd.Specs[0].(*ast.TypeSpec); ok {
866            if _ok := ts.Type.(*ast.StructType); ok {
867                structName = ts.Name.Name
868            }
869        }
870        apiInfo = p.Corpus.pkgAPIInfo[pkgName]
871    }
872
873    var out = w
874    var buf bytes.Buffer
875    if structName != "" {
876        out = &buf
877    }
878
879    mode := printer.TabIndent | printer.UseSpaces
880    err := (&printer.Config{ModemodeTabwidthp.TabWidth}).Fprint(&tconv{ppoutputout}, fsetx)
881    if err != nil {
882        log.Print(err)
883    }
884
885    // Add comments to struct fields saying which Go version introduced them.
886    if structName != "" {
887        fieldSince := apiInfo.fieldSince[structName]
888        typeSince := apiInfo.typeSince[structName]
889        // Add/rewrite comments on struct fields to note which Go version added them.
890        var buf2 bytes.Buffer
891        buf2.Grow(buf.Len() + len(" // Added in Go 1.n")*10)
892        bs := bufio.NewScanner(&buf)
893        for bs.Scan() {
894            line := bs.Bytes()
895            field := firstIdent(line)
896            var since string
897            if field != "" {
898                since = fieldSince[field]
899                if since != "" && since == typeSince {
900                    // Don't highlight field versions if they were the
901                    // same as the struct itself.
902                    since = ""
903                }
904            }
905            if since == "" {
906                buf2.Write(line)
907            } else {
908                if bytes.Contains(lineslashSlash) {
909                    line = bytes.TrimRight(line" \t.")
910                    buf2.Write(line)
911                    buf2.WriteString("; added in Go ")
912                } else {
913                    buf2.Write(line)
914                    buf2.WriteString(" // Go ")
915                }
916                buf2.WriteString(since)
917            }
918            buf2.WriteByte('\n')
919        }
920        w.Write(buf2.Bytes())
921    }
922}
923
924var slashSlash = []byte("//")
925
926// WriteNode writes x to w.
927// TODO(bgarcia) Is this method needed? It's just a wrapper for p.writeNode.
928func (p *PresentationWriteNode(w io.Writerfset *token.FileSetx interface{}) {
929    p.writeNode(wnilfsetx)
930}
931
932// firstIdent returns the first identifier in x.
933// This actually parses "identifiers" that begin with numbers too, but we
934// never feed it such input, so it's fine.
935func firstIdent(x []bytestring {
936    x = bytes.TrimSpace(x)
937    i := bytes.IndexFunc(x, func(r runebool { return !unicode.IsLetter(r) && !unicode.IsNumber(r) })
938    if i == -1 {
939        return string(x)
940    }
941    return string(x[:i])
942}
943
MembersX
filenameFunc.localname
Presentation.callgraph_htmlFunc
startsWithUppercase.r
replaceLeadingIndentation.BlockStmt.i
Presentation.WriteNode
Presentation.TemplateFuncs
Presentation.initFuncMap
Presentation.node_htmlFunc
Presentation.callgraph_htmlFunc.err
builtinPkgPath
scanIdentifier
Presentation.callgraph_htmlFunc.buf
linkedField.ids
PageInfo.Notes
srcBreadcrumbFunc.buf
srcBreadcrumbFunc.selectedSegment
srcBreadcrumbFunc.RangeStmt_13642.i
docLinkFunc.ident
Presentation.example_htmlFunc.RangeStmt_16102.BlockStmt.play
Presentation.callgraph_htmlFunc.name
Presentation.writeNode.BlockStmt.buf2
filterOutBuildAnnotations
PageInfo.CallGraphIndex
addStructFieldIDAttributes.st
PageInfo.FSet
PageInfo.PDoc
srcBreadcrumbFunc.selectedIndex
replaceLeadingIndentation.newIndent
Presentation.infoSnippet_htmlFunc
srcPosLinkFunc.high
Presentation.example_htmlFunc.p
Presentation.example_suffixFunc.name
Presentation.implements_htmlFunc.err
replaceLeadingIndentation.searchChars
Presentation.writeNode.err
infoKind_htmlFunc.info
Presentation.nodeFunc.buf
newPosLink_urlFunc.srcPosLinkFunc
startsWithUppercase.s
filenameFunc
Presentation.writeNode.w
bytes
Presentation.infoSnippet_htmlFunc.p
addStructFieldIDAttributes.buf
Presentation.writeNode.fset
infoKind_htmlFunc
queryLinkFunc.line
Presentation.example_htmlFunc.RangeStmt_16102.BlockStmt.BlockStmt.err
Presentation.writeNode.pkgName
multiply.b
addStructFieldIDAttributes.RangeStmt_6844.BlockStmt.fieldName
Presentation.example_htmlFunc.RangeStmt_16102.BlockStmt.wholeFile
splitExampleName.name
format
sanitizeFunc
srcBreadcrumbFunc.relpath
newPosLink_urlFunc.BlockStmt.end
srcPosLinkFunc.low
splitExampleName.i
Presentation.writeNode.pageInfo
Presentation.WriteNode.x
Presentation.example_htmlFunc.RangeStmt_16102.BlockStmt.n
firstIdent
utf8
scanIdentifier.BlockStmt.r
newPosLink_urlFunc.BlockStmt.relpath
firstIdent.x
fileInfoNameFunc.name
isStructTypeDecl.st
foreachLine.fn
Presentation.example_htmlFunc.RangeStmt_16102.BlockStmt.cnode
fileInfoNameFunc
Presentation.node_htmlFunc.BlockStmt.st
isStructTypeDecl.n
srcPosLinkFunc.line
Presentation.implements_htmlFunc.p
Presentation.methodset_htmlFunc.info
stripExampleSuffix.name
sanitizeFunc.buf
replaceLeadingIndentation.body
addStructFieldIDAttributes.RangeStmt_6844.f
PageInfo.Dirname
Presentation.example_nameFunc.s
replaceLeadingIndentation.state
Presentation.writeNode.BlockStmt.BlockStmt.since
Presentation.node_htmlFunc.BlockStmt.name
addStructFieldIDAttributes
isLetter
srcLinkFunc.s
replaceLeadingIndentation.BlockStmt.BlockStmt.BlockStmt.r
filenameFunc._
addStructFieldIDAttributes.name
Presentation.methodset_htmlFunc
Presentation.writeNode.BlockStmt.BlockStmt.field
bufio
Presentation.infoLineFunc.BlockStmt.index
scanIdentifier.v
PageInfo.IsEmpty
Presentation.example_htmlFunc
splitExampleName.suffix
Presentation.WriteNode.p
Presentation.TemplateFuncs.p
PageInfo.Err
fileInfoTimeFunc.fi
Presentation.node_htmlFunc.buf2
PageInfo.DirFlat
multiply
Presentation.nodeFunc.info
comment_htmlFunc.buf
PageInfo.PAst
Presentation.example_htmlFunc.RangeStmt_16102.eg
PageInfo.IsFiltered
docLinkFunc.s
Presentation.example_nameFunc.p
Presentation.example_suffixFunc
PageInfo.Mode
PageInfo.CallGraph
Presentation.callgraph_htmlFunc.info
Presentation.infoSnippet_htmlFunc.BlockStmt.index
Presentation.infoSnippet_htmlFunc.BlockStmt._
Presentation.node_htmlFunc.buf1
PageInfo.Examples
PageInfo.IsEmpty.info
filterOutBuildAnnotations.cg
Presentation.methodset_htmlFunc.typeName
Presentation.callgraph_htmlFunc.recv
noteTitle.note
replaceLeadingIndentation.codeState
Presentation.infoLineFunc.BlockStmt._
linkedField.id
isDigit
srcBreadcrumbFunc
newPosLink_urlFunc.BlockStmt.low
Presentation.implements_htmlFunc.typeName
fileInfoNameFunc.fi
isDigit.ch
Presentation.example_htmlFunc.buf
Presentation.example_nameFunc.name
srcLinkFunc
Presentation.example_htmlFunc.RangeStmt_16102.BlockStmt.BlockStmt.loc
Presentation.writeNode.BlockStmt.bs
htmltemplate
Presentation.infoLineFunc.p
foreachLine.BlockStmt.nl
srcBreadcrumbFunc.segments
Presentation.example_nameFunc.suffix
Presentation.implements_htmlFunc.info
noteTitle
Presentation.writeNode.p
Presentation.WriteNode.w
sanitizeFunc.j
Presentation.initFuncMap.p
filenameFunc.path
isLetter.ch
Presentation.FuncMap
docLinkFunc
Presentation.WriteNode.fset
scanIdentifier.n
Presentation.example_htmlFunc.info
Presentation.methodset_htmlFunc.err
startsWithUppercase
replaceLeadingIndentation.oldIndent
ast
Presentation.infoLineFunc.info
addStructFieldIDAttributes.newBuf
srcPosLinkFunc
fileInfoTimeFunc.t
foreachLine.in
linkedField.line
comment_htmlFunc
comment_htmlFunc.info
newPosLink_urlFunc.BlockStmt.pos
Presentation.writeNode.x
Presentation.FuncMap.p
startsWithUppercase._
splitExampleName.s
unicode
Presentation.nodeFunc
srcPosLinkFunc.buf
filterOutBuildAnnotations.RangeStmt_17682.i
printer
multiply.a
Presentation.node_htmlFunc.node
newPosLink_urlFunc
Presentation.example_nameFunc
Presentation.example_suffixFunc._
addStructFieldIDAttributes.BlockStmt.fieldName
newPosLink_urlFunc.BlockStmt.line
Presentation.example_suffixFunc.suffix
Presentation.implements_htmlFunc.buf
Presentation.methodset_htmlFunc.p
replaceLeadingIndentation.BlockStmt.BlockStmt.BlockStmt.size
Presentation.nodeFunc.node
Presentation.example_htmlFunc.funcName
replaceLeadingIndentation.buf
Presentation.writeNode.apiInfo
Presentation.writeNode.buf
queryLinkFunc
PageInfo.TypeInfoIndex
newPosLink_urlFunc.BlockStmt.high
Presentation.infoLineFunc
PageInfo.DirTime
pkgLinkFunc
queryLinkFunc.s
Presentation.example_htmlFunc.RangeStmt_16102.BlockStmt.name
fileInfoTimeFunc
Presentation.infoSnippet_htmlFunc.info
isStructTypeDecl.name
Presentation.example_htmlFunc.RangeStmt_16102.BlockStmt.err
Presentation.implements_htmlFunc
Presentation.writeNode
linkedField
PageInfo
Presentation.callgraph_htmlFunc.p
stripExampleSuffix
comment_htmlFunc.comment
Presentation.example_htmlFunc.RangeStmt_16102.BlockStmt.out
Presentation.methodset_htmlFunc.buf
stripExampleSuffix.i
scanIdentifier.BlockStmt.width
Presentation.example_htmlFunc.RangeStmt_16102.BlockStmt.BlockStmt.buf
splitExampleName
replaceLeadingIndentation
Presentation.writeNode.BlockStmt.BlockStmt.line
firstIdent.i
Presentation.node_htmlFunc.p
Presentation.node_htmlFunc.linkify
isStructTypeDecl
foreachLine
sanitizeFunc.i
srcToPkgLinkFunc
srcPosLinkFunc.s
Presentation.nodeFunc.p
Presentation.node_htmlFunc.info
PageInfo.IsMain
PageInfo.Dirs
newPosLink_urlFunc.BlockStmt.BlockStmt.p
queryLinkFunc.query
Presentation.example_htmlFunc.RangeStmt_16102.BlockStmt.code
Presentation.infoLineFunc.line
addStructFieldIDAttributes.needsLink
sanitizeFunc.src
PageInfo.AnalysisData
pkgLinkFunc.path
srcToPkgLinkFunc.relpath
Presentation.example_suffixFunc.p
Presentation.writeNode.structName
Presentation.writeNode.out
Members
X