...

Source file src/golang.org/x/tools/godoc/godoc.go

Documentation: golang.org/x/tools/godoc

     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(info *PageInfo, 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  
   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(&buf, info.PDoc, comment)
   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.
   364  func sanitizeFunc(src string) string {
   365  	buf := make([]byte, len(src))
   366  	j := 0      // buf index
   367  	comma := -1 // comma index if >= 0
   368  	for i := 0; i < 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  
   405  type 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  
   432  func (info *PageInfo) IsEmpty() bool {
   433  	return info.Err != nil || info.PAst == nil && info.PDoc == nil && info.Dirs == nil
   434  }
   435  
   436  func pkgLinkFunc(path string) string {
   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.
   447  func srcToPkgLinkFunc(relpath string) string {
   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>`, relpath, relpath[len("pkg/"):])
   454  }
   455  
   456  // srcBreadcrumbFunc converts each segment of relpath to a HTML <a>.
   457  // Each segment links to its corresponding src directories.
   458  func srcBreadcrumbFunc(relpath string) string {
   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  
   487  func newPosLink_urlFunc(srcPosLinkFunc func(s string, line, low, high int) string) func(info *PageInfo, n interface{}) string {
   488  	// n must be an ast.Node or a *doc.Note
   489  	return func(info *PageInfo, n interface{}) string {
   490  		var pos, end 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 low, high 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(relpath, line, low, high)
   518  	}
   519  }
   520  
   521  func srcPosLinkFunc(s string, line, low, high int) string {
   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", low, high) // 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  
   543  func srcLinkFunc(s string) string {
   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.
   556  func queryLinkFunc(s, query string, line int) string {
   557  	url := pathpkg.Clean("/"+s) + "?h=" + query
   558  	if line > 0 {
   559  		url += "#L" + strconv.Itoa(line)
   560  	}
   561  	return url
   562  }
   563  
   564  func docLinkFunc(s string, ident string) string {
   565  	return pathpkg.Clean("/pkg/"+s) + "/#" + ident
   566  }
   567  
   568  func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string {
   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{Node: eg.Code, Comments: eg.Comments}
   579  		code := p.node_htmlFunc(info, cnode, true)
   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(code, strings.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(&buf, info.FSet, eg.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  			Name, Doc, Code, Play, Output string
   621  		}{eg.Name, eg.Doc, code, play, out})
   622  		if err != nil {
   623  			log.Print(err)
   624  		}
   625  	}
   626  	return buf.String()
   627  }
   628  
   629  func 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)".
   648  func (p *Presentation) example_nameFunc(s string) string {
   649  	name, suffix := 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)".
   661  func (p *Presentation) example_suffixFunc(name string) string {
   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.
   668  func (p *Presentation) implements_htmlFunc(info *PageInfo, typeName string) string {
   669  	if p.ImplementsHTML == nil {
   670  		return ""
   671  	}
   672  	index, ok := 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.
   686  func (p *Presentation) methodset_htmlFunc(info *PageInfo, typeName string) string {
   687  	if p.MethodSetHTML == nil {
   688  		return ""
   689  	}
   690  	index, ok := 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.
   704  func (p *Presentation) callgraph_htmlFunc(info *PageInfo, recv, name string) string {
   705  	if p.CallGraphHTML == nil {
   706  		return ""
   707  	}
   708  	if recv != "" {
   709  		// Format must match (*ssa.Function).RelString().
   710  		name = fmt.Sprintf("(%s).%s", recv, name)
   711  	}
   712  	index, ok := 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  
   724  func noteTitle(note string) string {
   725  	return strings.Title(strings.ToLower(note))
   726  }
   727  
   728  func startsWithUppercase(s string) bool {
   729  	r, _ := utf8.DecodeRuneInString(s)
   730  	return unicode.IsUpper(r)
   731  }
   732  
   733  var 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.
   737  func stripExampleSuffix(name string) string {
   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  
   746  func splitExampleName(s string) (name, suffix 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).
   762  func replaceLeadingIndentation(body, oldIndent, newIndent string) string {
   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(body, oldIndent) {
   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(body, searchChars[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(body, oldIndent) {
   807  					buf.WriteString(newIndent)
   808  					body = body[len(oldIndent):]
   809  				}
   810  			}
   811  
   812  		case runeState:
   813  			switch c {
   814  			case '\\':
   815  				r, size := 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  				r, size := 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.
   849  func (p *Presentation) writeNode(w io.Writer, pageInfo *PageInfo, fset *token.FileSet, x 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 pkgName, structName string
   860  	var apiInfo pkgAPIVersions
   861  	if gd, ok := 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 ts, ok := 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{Mode: mode, Tabwidth: p.TabWidth}).Fprint(&tconv{p: p, output: out}, fset, x)
   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(line, slashSlash) {
   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  
   924  var slashSlash = []byte("//")
   925  
   926  // WriteNode writes x to w.
   927  // TODO(bgarcia) Is this method needed? It's just a wrapper for p.writeNode.
   928  func (p *Presentation) WriteNode(w io.Writer, fset *token.FileSet, x interface{}) {
   929  	p.writeNode(w, nil, fset, x)
   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.
   935  func firstIdent(x []byte) string {
   936  	x = bytes.TrimSpace(x)
   937  	i := bytes.IndexFunc(x, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsNumber(r) })
   938  	if i == -1 {
   939  		return string(x)
   940  	}
   941  	return string(x[:i])
   942  }
   943  

View as plain text