...

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

View as plain text