github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/godoc/godoc.go (about)

     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  	"bytes"
    14  	"fmt"
    15  	"go/ast"
    16  	"go/doc"
    17  	"go/format"
    18  	"go/printer"
    19  	"go/token"
    20  	htmltemplate "html/template"
    21  	"io"
    22  	"log"
    23  	"os"
    24  	pathpkg "path"
    25  	"regexp"
    26  	"strconv"
    27  	"strings"
    28  	"text/template"
    29  	"time"
    30  	"unicode"
    31  	"unicode/utf8"
    32  )
    33  
    34  // Fake relative package path for built-ins. Documentation for all globals
    35  // (not just exported ones) will be shown for packages in this directory.
    36  const builtinPkgPath = "builtin"
    37  
    38  // FuncMap defines template functions used in godoc templates.
    39  //
    40  // Convention: template function names ending in "_html" or "_url" produce
    41  //             HTML- or URL-escaped strings; all other function results may
    42  //             require explicit escaping in the template.
    43  func (p *Presentation) FuncMap() template.FuncMap {
    44  	p.initFuncMapOnce.Do(p.initFuncMap)
    45  	return p.funcMap
    46  }
    47  
    48  func (p *Presentation) TemplateFuncs() template.FuncMap {
    49  	p.initFuncMapOnce.Do(p.initFuncMap)
    50  	return p.templateFuncs
    51  }
    52  
    53  func (p *Presentation) initFuncMap() {
    54  	if p.Corpus == nil {
    55  		panic("nil Presentation.Corpus")
    56  	}
    57  	p.templateFuncs = template.FuncMap{
    58  		"code": p.code,
    59  	}
    60  	p.funcMap = template.FuncMap{
    61  		// various helpers
    62  		"filename": filenameFunc,
    63  		"repeat":   strings.Repeat,
    64  
    65  		// access to FileInfos (directory listings)
    66  		"fileInfoName": fileInfoNameFunc,
    67  		"fileInfoTime": fileInfoTimeFunc,
    68  
    69  		// access to search result information
    70  		"infoKind_html":    infoKind_htmlFunc,
    71  		"infoLine":         p.infoLineFunc,
    72  		"infoSnippet_html": p.infoSnippet_htmlFunc,
    73  
    74  		// formatting of AST nodes
    75  		"node":         p.nodeFunc,
    76  		"node_html":    p.node_htmlFunc,
    77  		"comment_html": comment_htmlFunc,
    78  		"comment_text": comment_textFunc,
    79  		"sanitize":     sanitizeFunc,
    80  
    81  		// support for URL attributes
    82  		"pkgLink":     pkgLinkFunc,
    83  		"srcLink":     srcLinkFunc,
    84  		"posLink_url": newPosLink_urlFunc(srcPosLinkFunc),
    85  		"docLink":     docLinkFunc,
    86  		"queryLink":   queryLinkFunc,
    87  
    88  		// formatting of Examples
    89  		"example_html":   p.example_htmlFunc,
    90  		"example_text":   p.example_textFunc,
    91  		"example_name":   p.example_nameFunc,
    92  		"example_suffix": p.example_suffixFunc,
    93  
    94  		// formatting of analysis information
    95  		"callgraph_html":  p.callgraph_htmlFunc,
    96  		"implements_html": p.implements_htmlFunc,
    97  		"methodset_html":  p.methodset_htmlFunc,
    98  
    99  		// formatting of Notes
   100  		"noteTitle": noteTitle,
   101  
   102  		// Number operation
   103  		"multiply": multiply,
   104  	}
   105  	if p.URLForSrc != nil {
   106  		p.funcMap["srcLink"] = p.URLForSrc
   107  	}
   108  	if p.URLForSrcPos != nil {
   109  		p.funcMap["posLink_url"] = newPosLink_urlFunc(p.URLForSrcPos)
   110  	}
   111  	if p.URLForSrcQuery != nil {
   112  		p.funcMap["queryLink"] = p.URLForSrcQuery
   113  	}
   114  }
   115  
   116  func multiply(a, b int) int { return a * b }
   117  
   118  func filenameFunc(path string) string {
   119  	_, localname := pathpkg.Split(path)
   120  	return localname
   121  }
   122  
   123  func fileInfoNameFunc(fi os.FileInfo) string {
   124  	name := fi.Name()
   125  	if fi.IsDir() {
   126  		name += "/"
   127  	}
   128  	return name
   129  }
   130  
   131  func fileInfoTimeFunc(fi os.FileInfo) string {
   132  	if t := fi.ModTime(); t.Unix() != 0 {
   133  		return t.Local().String()
   134  	}
   135  	return "" // don't return epoch if time is obviously not set
   136  }
   137  
   138  // The strings in infoKinds must be properly html-escaped.
   139  var infoKinds = [nKinds]string{
   140  	PackageClause: "package clause",
   141  	ImportDecl:    "import decl",
   142  	ConstDecl:     "const decl",
   143  	TypeDecl:      "type decl",
   144  	VarDecl:       "var decl",
   145  	FuncDecl:      "func decl",
   146  	MethodDecl:    "method decl",
   147  	Use:           "use",
   148  }
   149  
   150  func infoKind_htmlFunc(info SpotInfo) string {
   151  	return infoKinds[info.Kind()] // infoKind entries are html-escaped
   152  }
   153  
   154  func (p *Presentation) infoLineFunc(info SpotInfo) int {
   155  	line := info.Lori()
   156  	if info.IsIndex() {
   157  		index, _ := p.Corpus.searchIndex.Get()
   158  		if index != nil {
   159  			line = index.(*Index).Snippet(line).Line
   160  		} else {
   161  			// no line information available because
   162  			// we don't have an index - this should
   163  			// never happen; be conservative and don't
   164  			// crash
   165  			line = 0
   166  		}
   167  	}
   168  	return line
   169  }
   170  
   171  func (p *Presentation) infoSnippet_htmlFunc(info SpotInfo) string {
   172  	if info.IsIndex() {
   173  		index, _ := p.Corpus.searchIndex.Get()
   174  		// Snippet.Text was HTML-escaped when it was generated
   175  		return index.(*Index).Snippet(info.Lori()).Text
   176  	}
   177  	return `<span class="alert">no snippet text available</span>`
   178  }
   179  
   180  func (p *Presentation) nodeFunc(info *PageInfo, node interface{}) string {
   181  	var buf bytes.Buffer
   182  	p.writeNode(&buf, info.FSet, node)
   183  	return buf.String()
   184  }
   185  
   186  func (p *Presentation) node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string {
   187  	var buf1 bytes.Buffer
   188  	p.writeNode(&buf1, info.FSet, node)
   189  
   190  	var buf2 bytes.Buffer
   191  	if n, _ := node.(ast.Node); n != nil && linkify && p.DeclLinks {
   192  		LinkifyText(&buf2, buf1.Bytes(), n)
   193  	} else {
   194  		FormatText(&buf2, buf1.Bytes(), -1, true, "", nil)
   195  	}
   196  
   197  	return buf2.String()
   198  }
   199  
   200  func comment_htmlFunc(comment string) string {
   201  	var buf bytes.Buffer
   202  	// TODO(gri) Provide list of words (e.g. function parameters)
   203  	//           to be emphasized by ToHTML.
   204  	doc.ToHTML(&buf, comment, nil) // does html-escaping
   205  	return buf.String()
   206  }
   207  
   208  // punchCardWidth is the number of columns of fixed-width
   209  // characters to assume when wrapping text.  Very few people
   210  // use terminals or cards smaller than 80 characters, so 80 it is.
   211  // We do not try to sniff the environment or the tty to adapt to
   212  // the situation; instead, by using a constant we make sure that
   213  // godoc always produces the same output regardless of context,
   214  // a consistency that is lost otherwise.  For example, if we sniffed
   215  // the environment or tty, then http://golang.org/pkg/math/?m=text
   216  // would depend on the width of the terminal where godoc started,
   217  // which is clearly bogus.  More generally, the Unix tools that behave
   218  // differently when writing to a tty than when writing to a file have
   219  // a history of causing confusion (compare `ls` and `ls | cat`), and we
   220  // want to avoid that mistake here.
   221  const punchCardWidth = 80
   222  
   223  func containsOnlySpace(buf []byte) bool {
   224  	isNotSpace := func(r rune) bool { return !unicode.IsSpace(r) }
   225  	return bytes.IndexFunc(buf, isNotSpace) == -1
   226  }
   227  
   228  func comment_textFunc(comment, indent, preIndent string) string {
   229  	var buf bytes.Buffer
   230  	doc.ToText(&buf, comment, indent, preIndent, punchCardWidth-2*len(indent))
   231  	if containsOnlySpace(buf.Bytes()) {
   232  		return ""
   233  	}
   234  	return buf.String()
   235  }
   236  
   237  // sanitizeFunc sanitizes the argument src by replacing newlines with
   238  // blanks, removing extra blanks, and by removing trailing whitespace
   239  // and commas before closing parentheses.
   240  func sanitizeFunc(src string) string {
   241  	buf := make([]byte, len(src))
   242  	j := 0      // buf index
   243  	comma := -1 // comma index if >= 0
   244  	for i := 0; i < len(src); i++ {
   245  		ch := src[i]
   246  		switch ch {
   247  		case '\t', '\n', ' ':
   248  			// ignore whitespace at the beginning, after a blank, or after opening parentheses
   249  			if j == 0 {
   250  				continue
   251  			}
   252  			if p := buf[j-1]; p == ' ' || p == '(' || p == '{' || p == '[' {
   253  				continue
   254  			}
   255  			// replace all whitespace with blanks
   256  			ch = ' '
   257  		case ',':
   258  			comma = j
   259  		case ')', '}', ']':
   260  			// remove any trailing comma
   261  			if comma >= 0 {
   262  				j = comma
   263  			}
   264  			// remove any trailing whitespace
   265  			if j > 0 && buf[j-1] == ' ' {
   266  				j--
   267  			}
   268  		default:
   269  			comma = -1
   270  		}
   271  		buf[j] = ch
   272  		j++
   273  	}
   274  	// remove trailing blank, if any
   275  	if j > 0 && buf[j-1] == ' ' {
   276  		j--
   277  	}
   278  	return string(buf[:j])
   279  }
   280  
   281  type PageInfo struct {
   282  	Dirname string // directory containing the package
   283  	Err     error  // error or nil
   284  	Share   bool   // show share button on examples
   285  
   286  	// package info
   287  	FSet       *token.FileSet         // nil if no package documentation
   288  	PDoc       *doc.Package           // nil if no package documentation
   289  	Examples   []*doc.Example         // nil if no example code
   290  	Notes      map[string][]*doc.Note // nil if no package Notes
   291  	PAst       map[string]*ast.File   // nil if no AST with package exports
   292  	IsMain     bool                   // true for package main
   293  	IsFiltered bool                   // true if results were filtered
   294  
   295  	// analysis info
   296  	TypeInfoIndex  map[string]int  // index of JSON datum for type T (if -analysis=type)
   297  	AnalysisData   htmltemplate.JS // array of TypeInfoJSON values
   298  	CallGraph      htmltemplate.JS // array of PCGNodeJSON values    (if -analysis=pointer)
   299  	CallGraphIndex map[string]int  // maps func name to index in CallGraph
   300  
   301  	// directory info
   302  	Dirs    *DirList  // nil if no directory information
   303  	DirTime time.Time // directory time stamp
   304  	DirFlat bool      // if set, show directory in a flat (non-indented) manner
   305  }
   306  
   307  func (info *PageInfo) IsEmpty() bool {
   308  	return info.Err != nil || info.PAst == nil && info.PDoc == nil && info.Dirs == nil
   309  }
   310  
   311  func pkgLinkFunc(path string) string {
   312  	// because of the irregular mapping under goroot
   313  	// we need to correct certain relative paths
   314  	path = strings.TrimPrefix(path, "/")
   315  	path = strings.TrimPrefix(path, "src/")
   316  	path = strings.TrimPrefix(path, "pkg/")
   317  	return "pkg/" + path
   318  }
   319  
   320  func newPosLink_urlFunc(srcPosLinkFunc func(s string, line, low, high int) string) func(info *PageInfo, n interface{}) string {
   321  	// n must be an ast.Node or a *doc.Note
   322  	return func(info *PageInfo, n interface{}) string {
   323  		var pos, end token.Pos
   324  
   325  		switch n := n.(type) {
   326  		case ast.Node:
   327  			pos = n.Pos()
   328  			end = n.End()
   329  		case *doc.Note:
   330  			pos = n.Pos
   331  			end = n.End
   332  		default:
   333  			panic(fmt.Sprintf("wrong type for posLink_url template formatter: %T", n))
   334  		}
   335  
   336  		var relpath string
   337  		var line int
   338  		var low, high int // selection offset range
   339  
   340  		if pos.IsValid() {
   341  			p := info.FSet.Position(pos)
   342  			relpath = p.Filename
   343  			line = p.Line
   344  			low = p.Offset
   345  		}
   346  		if end.IsValid() {
   347  			high = info.FSet.Position(end).Offset
   348  		}
   349  
   350  		return srcPosLinkFunc(relpath, line, low, high)
   351  	}
   352  }
   353  
   354  func srcPosLinkFunc(s string, line, low, high int) string {
   355  	s = srcLinkFunc(s)
   356  	var buf bytes.Buffer
   357  	template.HTMLEscape(&buf, []byte(s))
   358  	// selection ranges are of form "s=low:high"
   359  	if low < high {
   360  		fmt.Fprintf(&buf, "?s=%d:%d", low, high) // no need for URL escaping
   361  		// if we have a selection, position the page
   362  		// such that the selection is a bit below the top
   363  		line -= 10
   364  		if line < 1 {
   365  			line = 1
   366  		}
   367  	}
   368  	// line id's in html-printed source are of the
   369  	// form "L%d" where %d stands for the line number
   370  	if line > 0 {
   371  		fmt.Fprintf(&buf, "#L%d", line) // no need for URL escaping
   372  	}
   373  	return buf.String()
   374  }
   375  
   376  func srcLinkFunc(s string) string {
   377  	s = pathpkg.Clean("/" + s)
   378  	if !strings.HasPrefix(s, "/src/") {
   379  		s = "/src" + s
   380  	}
   381  	return s
   382  }
   383  
   384  // queryLinkFunc returns a URL for a line in a source file with a highlighted
   385  // query term.
   386  // s is expected to be a path to a source file.
   387  // query is expected to be a string that has already been appropriately escaped
   388  // for use in a URL query.
   389  func queryLinkFunc(s, query string, line int) string {
   390  	url := pathpkg.Clean("/"+s) + "?h=" + query
   391  	if line > 0 {
   392  		url += "#L" + strconv.Itoa(line)
   393  	}
   394  	return url
   395  }
   396  
   397  func docLinkFunc(s string, ident string) string {
   398  	return pathpkg.Clean("/pkg/"+s) + "/#" + ident
   399  }
   400  
   401  func (p *Presentation) example_textFunc(info *PageInfo, funcName, indent string) string {
   402  	if !p.ShowExamples {
   403  		return ""
   404  	}
   405  
   406  	var buf bytes.Buffer
   407  	first := true
   408  	for _, eg := range info.Examples {
   409  		name := stripExampleSuffix(eg.Name)
   410  		if name != funcName {
   411  			continue
   412  		}
   413  
   414  		if !first {
   415  			buf.WriteString("\n")
   416  		}
   417  		first = false
   418  
   419  		// print code
   420  		cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
   421  		var buf1 bytes.Buffer
   422  		p.writeNode(&buf1, info.FSet, cnode)
   423  		code := buf1.String()
   424  		// Additional formatting if this is a function body.
   425  		if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
   426  			// remove surrounding braces
   427  			code = code[1 : n-1]
   428  			// unindent
   429  			code = strings.Replace(code, "\n    ", "\n", -1)
   430  		}
   431  		code = strings.Trim(code, "\n")
   432  		code = strings.Replace(code, "\n", "\n\t", -1)
   433  
   434  		buf.WriteString(indent)
   435  		buf.WriteString("Example:\n\t")
   436  		buf.WriteString(code)
   437  		buf.WriteString("\n\n")
   438  	}
   439  	return buf.String()
   440  }
   441  
   442  func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string {
   443  	var buf bytes.Buffer
   444  	for _, eg := range info.Examples {
   445  		name := stripExampleSuffix(eg.Name)
   446  
   447  		if name != funcName {
   448  			continue
   449  		}
   450  
   451  		// print code
   452  		cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
   453  		code := p.node_htmlFunc(info, cnode, true)
   454  		out := eg.Output
   455  		wholeFile := true
   456  
   457  		// Additional formatting if this is a function body.
   458  		if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
   459  			wholeFile = false
   460  			// remove surrounding braces
   461  			code = code[1 : n-1]
   462  			// unindent
   463  			code = strings.Replace(code, "\n    ", "\n", -1)
   464  			// remove output comment
   465  			if loc := exampleOutputRx.FindStringIndex(code); loc != nil {
   466  				code = strings.TrimSpace(code[:loc[0]])
   467  			}
   468  		}
   469  
   470  		// Write out the playground code in standard Go style
   471  		// (use tabs, no comment highlight, etc).
   472  		play := ""
   473  		if eg.Play != nil && p.ShowPlayground {
   474  			var buf bytes.Buffer
   475  			if err := format.Node(&buf, info.FSet, eg.Play); err != nil {
   476  				log.Print(err)
   477  			} else {
   478  				play = buf.String()
   479  			}
   480  		}
   481  
   482  		// Drop output, as the output comment will appear in the code.
   483  		if wholeFile && play == "" {
   484  			out = ""
   485  		}
   486  
   487  		if p.ExampleHTML == nil {
   488  			out = ""
   489  			return ""
   490  		}
   491  
   492  		err := p.ExampleHTML.Execute(&buf, struct {
   493  			Name, Doc, Code, Play, Output string
   494  			Share                         bool
   495  		}{eg.Name, eg.Doc, code, play, out, info.Share})
   496  		if err != nil {
   497  			log.Print(err)
   498  		}
   499  	}
   500  	return buf.String()
   501  }
   502  
   503  // example_nameFunc takes an example function name and returns its display
   504  // name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
   505  func (p *Presentation) example_nameFunc(s string) string {
   506  	name, suffix := splitExampleName(s)
   507  	// replace _ with . for method names
   508  	name = strings.Replace(name, "_", ".", 1)
   509  	// use "Package" if no name provided
   510  	if name == "" {
   511  		name = "Package"
   512  	}
   513  	return name + suffix
   514  }
   515  
   516  // example_suffixFunc takes an example function name and returns its suffix in
   517  // parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
   518  func (p *Presentation) example_suffixFunc(name string) string {
   519  	_, suffix := splitExampleName(name)
   520  	return suffix
   521  }
   522  
   523  // implements_html returns the "> Implements" toggle for a package-level named type.
   524  // Its contents are populated from JSON data by client-side JS at load time.
   525  func (p *Presentation) implements_htmlFunc(info *PageInfo, typeName string) string {
   526  	if p.ImplementsHTML == nil {
   527  		return ""
   528  	}
   529  	index, ok := info.TypeInfoIndex[typeName]
   530  	if !ok {
   531  		return ""
   532  	}
   533  	var buf bytes.Buffer
   534  	err := p.ImplementsHTML.Execute(&buf, struct{ Index int }{index})
   535  	if err != nil {
   536  		log.Print(err)
   537  	}
   538  	return buf.String()
   539  }
   540  
   541  // methodset_html returns the "> Method set" toggle for a package-level named type.
   542  // Its contents are populated from JSON data by client-side JS at load time.
   543  func (p *Presentation) methodset_htmlFunc(info *PageInfo, typeName string) string {
   544  	if p.MethodSetHTML == nil {
   545  		return ""
   546  	}
   547  	index, ok := info.TypeInfoIndex[typeName]
   548  	if !ok {
   549  		return ""
   550  	}
   551  	var buf bytes.Buffer
   552  	err := p.MethodSetHTML.Execute(&buf, struct{ Index int }{index})
   553  	if err != nil {
   554  		log.Print(err)
   555  	}
   556  	return buf.String()
   557  }
   558  
   559  // callgraph_html returns the "> Call graph" toggle for a package-level func.
   560  // Its contents are populated from JSON data by client-side JS at load time.
   561  func (p *Presentation) callgraph_htmlFunc(info *PageInfo, recv, name string) string {
   562  	if p.CallGraphHTML == nil {
   563  		return ""
   564  	}
   565  	if recv != "" {
   566  		// Format must match (*ssa.Function).RelString().
   567  		name = fmt.Sprintf("(%s).%s", recv, name)
   568  	}
   569  	index, ok := info.CallGraphIndex[name]
   570  	if !ok {
   571  		return ""
   572  	}
   573  	var buf bytes.Buffer
   574  	err := p.CallGraphHTML.Execute(&buf, struct{ Index int }{index})
   575  	if err != nil {
   576  		log.Print(err)
   577  	}
   578  	return buf.String()
   579  }
   580  
   581  func noteTitle(note string) string {
   582  	return strings.Title(strings.ToLower(note))
   583  }
   584  
   585  func startsWithUppercase(s string) bool {
   586  	r, _ := utf8.DecodeRuneInString(s)
   587  	return unicode.IsUpper(r)
   588  }
   589  
   590  var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*output:`)
   591  
   592  // stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name
   593  // while keeping uppercase Braz in Foo_Braz.
   594  func stripExampleSuffix(name string) string {
   595  	if i := strings.LastIndex(name, "_"); i != -1 {
   596  		if i < len(name)-1 && !startsWithUppercase(name[i+1:]) {
   597  			name = name[:i]
   598  		}
   599  	}
   600  	return name
   601  }
   602  
   603  func splitExampleName(s string) (name, suffix string) {
   604  	i := strings.LastIndex(s, "_")
   605  	if 0 <= i && i < len(s)-1 && !startsWithUppercase(s[i+1:]) {
   606  		name = s[:i]
   607  		suffix = " (" + strings.Title(s[i+1:]) + ")"
   608  		return
   609  	}
   610  	name = s
   611  	return
   612  }
   613  
   614  // Write an AST node to w.
   615  func (p *Presentation) writeNode(w io.Writer, fset *token.FileSet, x interface{}) {
   616  	// convert trailing tabs into spaces using a tconv filter
   617  	// to ensure a good outcome in most browsers (there may still
   618  	// be tabs in comments and strings, but converting those into
   619  	// the right number of spaces is much harder)
   620  	//
   621  	// TODO(gri) rethink printer flags - perhaps tconv can be eliminated
   622  	//           with an another printer mode (which is more efficiently
   623  	//           implemented in the printer than here with another layer)
   624  	mode := printer.TabIndent | printer.UseSpaces
   625  	err := (&printer.Config{Mode: mode, Tabwidth: p.TabWidth}).Fprint(&tconv{p: p, output: w}, fset, x)
   626  	if err != nil {
   627  		log.Print(err)
   628  	}
   629  }
   630  
   631  // WriteNode writes x to w.
   632  // TODO(bgarcia) Is this method needed? It's just a wrapper for p.writeNode.
   633  func (p *Presentation) WriteNode(w io.Writer, fset *token.FileSet, x interface{}) {
   634  	p.writeNode(w, fset, x)
   635  }