Source file src/cmd/go/internal/doc/pkg.go

     1  // Copyright 2015 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 doc
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"fmt"
    11  	"go/ast"
    12  	"go/doc"
    13  	"go/format"
    14  	"go/parser"
    15  	"go/printer"
    16  	"go/token"
    17  	"io"
    18  	"io/fs"
    19  	"log"
    20  	"path/filepath"
    21  	"strings"
    22  	"unicode"
    23  	"unicode/utf8"
    24  
    25  	"cmd/go/internal/cfg"
    26  	"cmd/go/internal/load"
    27  )
    28  
    29  const (
    30  	punchedCardWidth = 80
    31  	indent           = "    "
    32  )
    33  
    34  type Package struct {
    35  	writer      io.Writer    // Destination for output.
    36  	name        string       // Package name, json for encoding/json.
    37  	userPath    string       // String the user used to find this package.
    38  	pkg         *ast.Package // Parsed package.
    39  	file        *ast.File    // Merged from all files in the package
    40  	doc         *doc.Package
    41  	build       *load.Package
    42  	typedValue  map[*doc.Value]bool // Consts and vars related to types.
    43  	constructor map[*doc.Func]bool  // Constructors.
    44  	fs          *token.FileSet      // Needed for printing.
    45  	buf         pkgBuffer
    46  }
    47  
    48  func (pkg *Package) ToText(w io.Writer, text, prefix, codePrefix string) {
    49  	d := pkg.doc.Parser().Parse(text)
    50  	pr := pkg.doc.Printer()
    51  	pr.TextPrefix = prefix
    52  	pr.TextCodePrefix = codePrefix
    53  	w.Write(pr.Text(d))
    54  }
    55  
    56  // pkgBuffer is a wrapper for bytes.Buffer that prints a package clause the
    57  // first time Write is called.
    58  type pkgBuffer struct {
    59  	pkg     *Package
    60  	printed bool // Prevent repeated package clauses.
    61  	bytes.Buffer
    62  }
    63  
    64  func (pb *pkgBuffer) Write(p []byte) (int, error) {
    65  	pb.packageClause()
    66  	return pb.Buffer.Write(p)
    67  }
    68  
    69  func (pb *pkgBuffer) packageClause() {
    70  	if !pb.printed {
    71  		pb.printed = true
    72  		// Only show package clause for commands if requested explicitly.
    73  		if pb.pkg.pkg.Name != "main" || showCmd {
    74  			pb.pkg.packageClause()
    75  		}
    76  	}
    77  }
    78  
    79  type PackageError string // type returned by pkg.Fatalf.
    80  
    81  func (p PackageError) Error() string {
    82  	return string(p)
    83  }
    84  
    85  // prettyPath returns a version of the package path that is suitable for an
    86  // error message. It obeys the import comment if present. Also, since
    87  // pkg.build.ImportPath is sometimes the unhelpful "" or ".", it looks for a
    88  // directory name in GOROOT or GOPATH if that happens.
    89  func (pkg *Package) prettyPath() string {
    90  	path := pkg.build.ImportComment
    91  	if path == "" {
    92  		path = pkg.build.ImportPath
    93  	}
    94  	if path != "." && path != "" {
    95  		return path
    96  	}
    97  	// Convert the source directory into a more useful path.
    98  	// Also convert everything to slash-separated paths for uniform handling.
    99  	path = filepath.Clean(filepath.ToSlash(pkg.build.Dir))
   100  	// Can we find a decent prefix?
   101  	if cfg.GOROOT != "" {
   102  		goroot := filepath.Join(cfg.GOROOT, "src")
   103  		if p, ok := trim(path, filepath.ToSlash(goroot)); ok {
   104  			return p
   105  		}
   106  	}
   107  	for _, gopath := range splitGopath() {
   108  		if p, ok := trim(path, filepath.ToSlash(gopath)); ok {
   109  			return p
   110  		}
   111  	}
   112  	return path
   113  }
   114  
   115  // trim trims the directory prefix from the path, paying attention
   116  // to the path separator. If they are the same string or the prefix
   117  // is not present the original is returned. The boolean reports whether
   118  // the prefix is present. That path and prefix have slashes for separators.
   119  func trim(path, prefix string) (string, bool) {
   120  	if !strings.HasPrefix(path, prefix) {
   121  		return path, false
   122  	}
   123  	if path == prefix {
   124  		return path, true
   125  	}
   126  	if path[len(prefix)] == '/' {
   127  		return path[len(prefix)+1:], true
   128  	}
   129  	return path, false // Textual prefix but not a path prefix.
   130  }
   131  
   132  // pkg.Fatalf is like log.Fatalf, but panics so it can be recovered in the
   133  // main do function, so it doesn't cause an exit. Allows testing to work
   134  // without running a subprocess. The log prefix will be added when
   135  // logged in main; it is not added here.
   136  func (pkg *Package) Fatalf(format string, args ...any) {
   137  	panic(PackageError(fmt.Sprintf(format, args...)))
   138  }
   139  
   140  // parsePackage turns the build package we found into a parsed package
   141  // we can then use to generate documentation.
   142  func parsePackage(writer io.Writer, pkg *load.Package, userPath string) *Package {
   143  	// include tells parser.ParseDir which files to include.
   144  	// That means the file must be in the build package's GoFiles, CgoFiles,
   145  	// TestGoFiles or XTestGoFiles list only (no tag-ignored files, swig or
   146  	// other non-Go files).
   147  	include := func(info fs.FileInfo) bool {
   148  		files := [][]string{pkg.GoFiles, pkg.CgoFiles, pkg.TestGoFiles, pkg.XTestGoFiles}
   149  		for _, f := range files {
   150  			for _, name := range f {
   151  				if name == info.Name() {
   152  					return true
   153  				}
   154  			}
   155  		}
   156  		return false
   157  	}
   158  	fset := token.NewFileSet()
   159  	pkgs, err := parser.ParseDir(fset, pkg.Dir, include, parser.ParseComments)
   160  	if err != nil {
   161  		log.Fatal(err)
   162  	}
   163  	if len(pkgs) == 0 {
   164  		log.Fatalf("no source-code package in directory %s", pkg.Dir)
   165  	}
   166  	astPkg := pkgs[pkg.Name]
   167  
   168  	// TODO: go/doc does not include typed constants in the constants
   169  	// list, which is what we want. For instance, time.Sunday is of type
   170  	// time.Weekday, so it is defined in the type but not in the
   171  	// Consts list for the package. This prevents
   172  	//	go doc time.Sunday
   173  	// from finding the symbol. Work around this for now, but we
   174  	// should fix it in go/doc.
   175  	// A similar story applies to factory functions.
   176  	mode := doc.AllDecls
   177  	if showSrc {
   178  		mode |= doc.PreserveAST // See comment for Package.emit.
   179  	}
   180  	var allGoFiles []*ast.File
   181  	for _, p := range pkgs {
   182  		for _, f := range p.Files {
   183  			allGoFiles = append(allGoFiles, f)
   184  		}
   185  	}
   186  	docPkg, err := doc.NewFromFiles(fset, allGoFiles, pkg.ImportPath, mode)
   187  	if err != nil {
   188  		log.Fatal(err)
   189  	}
   190  	typedValue := make(map[*doc.Value]bool)
   191  	constructor := make(map[*doc.Func]bool)
   192  	for _, typ := range docPkg.Types {
   193  		docPkg.Consts = append(docPkg.Consts, typ.Consts...)
   194  		docPkg.Vars = append(docPkg.Vars, typ.Vars...)
   195  		docPkg.Funcs = append(docPkg.Funcs, typ.Funcs...)
   196  		if isExported(typ.Name) {
   197  			for _, value := range typ.Consts {
   198  				typedValue[value] = true
   199  			}
   200  			for _, value := range typ.Vars {
   201  				typedValue[value] = true
   202  			}
   203  			for _, fun := range typ.Funcs {
   204  				// We don't count it as a constructor bound to the type
   205  				// if the type itself is not exported.
   206  				constructor[fun] = true
   207  			}
   208  		}
   209  	}
   210  
   211  	p := &Package{
   212  		writer:      writer,
   213  		name:        pkg.Name,
   214  		userPath:    userPath,
   215  		pkg:         astPkg,
   216  		file:        ast.MergePackageFiles(astPkg, 0),
   217  		doc:         docPkg,
   218  		typedValue:  typedValue,
   219  		constructor: constructor,
   220  		build:       pkg,
   221  		fs:          fset,
   222  	}
   223  	p.buf.pkg = p
   224  	return p
   225  }
   226  
   227  func (pkg *Package) Printf(format string, args ...any) {
   228  	fmt.Fprintf(&pkg.buf, format, args...)
   229  }
   230  
   231  func (pkg *Package) flush() {
   232  	_, err := pkg.writer.Write(pkg.buf.Bytes())
   233  	if err != nil {
   234  		log.Fatal(err)
   235  	}
   236  	pkg.buf.Reset() // Not needed, but it's a flush.
   237  }
   238  
   239  var newlineBytes = []byte("\n\n") // We never ask for more than 2.
   240  
   241  // newlines guarantees there are n newlines at the end of the buffer.
   242  func (pkg *Package) newlines(n int) {
   243  	for !bytes.HasSuffix(pkg.buf.Bytes(), newlineBytes[:n]) {
   244  		pkg.buf.WriteRune('\n')
   245  	}
   246  }
   247  
   248  // emit prints the node. If showSrc is true, it ignores the provided comment,
   249  // assuming the comment is in the node itself. Otherwise, the go/doc package
   250  // clears the stuff we don't want to print anyway. It's a bit of a magic trick.
   251  func (pkg *Package) emit(comment string, node ast.Node) {
   252  	if node != nil {
   253  		var arg any = node
   254  		if showSrc {
   255  			// Need an extra little dance to get internal comments to appear.
   256  			arg = &printer.CommentedNode{
   257  				Node:     node,
   258  				Comments: pkg.file.Comments,
   259  			}
   260  		}
   261  		err := format.Node(&pkg.buf, pkg.fs, arg)
   262  		if err != nil {
   263  			log.Fatal(err)
   264  		}
   265  		if comment != "" && !showSrc {
   266  			pkg.newlines(1)
   267  			pkg.ToText(&pkg.buf, comment, indent, indent+indent)
   268  			pkg.newlines(2) // Blank line after comment to separate from next item.
   269  		} else {
   270  			pkg.newlines(1)
   271  		}
   272  	}
   273  }
   274  
   275  // oneLineNode returns a one-line summary of the given input node.
   276  func (pkg *Package) oneLineNode(node ast.Node) string {
   277  	const maxDepth = 10
   278  	return pkg.oneLineNodeDepth(node, maxDepth)
   279  }
   280  
   281  // oneLineNodeDepth returns a one-line summary of the given input node.
   282  // The depth specifies the maximum depth when traversing the AST.
   283  func (pkg *Package) oneLineNodeDepth(node ast.Node, depth int) string {
   284  	const dotDotDot = "..."
   285  	if depth == 0 {
   286  		return dotDotDot
   287  	}
   288  	depth--
   289  
   290  	switch n := node.(type) {
   291  	case nil:
   292  		return ""
   293  
   294  	case *ast.GenDecl:
   295  		// Formats const and var declarations.
   296  		trailer := ""
   297  		if len(n.Specs) > 1 {
   298  			trailer = " " + dotDotDot
   299  		}
   300  
   301  		// Find the first relevant spec.
   302  		typ := ""
   303  		for i, spec := range n.Specs {
   304  			valueSpec := spec.(*ast.ValueSpec) // Must succeed; we can't mix types in one GenDecl.
   305  
   306  			// The type name may carry over from a previous specification in the
   307  			// case of constants and iota.
   308  			if valueSpec.Type != nil {
   309  				typ = fmt.Sprintf(" %s", pkg.oneLineNodeDepth(valueSpec.Type, depth))
   310  			} else if len(valueSpec.Values) > 0 {
   311  				typ = ""
   312  			}
   313  
   314  			if !isExported(valueSpec.Names[0].Name) {
   315  				continue
   316  			}
   317  			val := ""
   318  			if i < len(valueSpec.Values) && valueSpec.Values[i] != nil {
   319  				val = fmt.Sprintf(" = %s", pkg.oneLineNodeDepth(valueSpec.Values[i], depth))
   320  			}
   321  			return fmt.Sprintf("%s %s%s%s%s", n.Tok, valueSpec.Names[0], typ, val, trailer)
   322  		}
   323  		return ""
   324  
   325  	case *ast.FuncDecl:
   326  		// Formats func declarations.
   327  		name := n.Name.Name
   328  		recv := pkg.oneLineNodeDepth(n.Recv, depth)
   329  		if len(recv) > 0 {
   330  			recv = "(" + recv + ") "
   331  		}
   332  		fnc := pkg.oneLineNodeDepth(n.Type, depth)
   333  		fnc = strings.TrimPrefix(fnc, "func")
   334  		return fmt.Sprintf("func %s%s%s", recv, name, fnc)
   335  
   336  	case *ast.TypeSpec:
   337  		sep := " "
   338  		if n.Assign.IsValid() {
   339  			sep = " = "
   340  		}
   341  		tparams := pkg.formatTypeParams(n.TypeParams, depth)
   342  		return fmt.Sprintf("type %s%s%s%s", n.Name.Name, tparams, sep, pkg.oneLineNodeDepth(n.Type, depth))
   343  
   344  	case *ast.FuncType:
   345  		var params []string
   346  		if n.Params != nil {
   347  			for _, field := range n.Params.List {
   348  				params = append(params, pkg.oneLineField(field, depth))
   349  			}
   350  		}
   351  		needParens := false
   352  		var results []string
   353  		if n.Results != nil {
   354  			needParens = needParens || len(n.Results.List) > 1
   355  			for _, field := range n.Results.List {
   356  				needParens = needParens || len(field.Names) > 0
   357  				results = append(results, pkg.oneLineField(field, depth))
   358  			}
   359  		}
   360  
   361  		tparam := pkg.formatTypeParams(n.TypeParams, depth)
   362  		param := joinStrings(params)
   363  		if len(results) == 0 {
   364  			return fmt.Sprintf("func%s(%s)", tparam, param)
   365  		}
   366  		result := joinStrings(results)
   367  		if !needParens {
   368  			return fmt.Sprintf("func%s(%s) %s", tparam, param, result)
   369  		}
   370  		return fmt.Sprintf("func%s(%s) (%s)", tparam, param, result)
   371  
   372  	case *ast.StructType:
   373  		if n.Fields == nil || len(n.Fields.List) == 0 {
   374  			return "struct{}"
   375  		}
   376  		return "struct{ ... }"
   377  
   378  	case *ast.InterfaceType:
   379  		if n.Methods == nil || len(n.Methods.List) == 0 {
   380  			return "interface{}"
   381  		}
   382  		return "interface{ ... }"
   383  
   384  	case *ast.FieldList:
   385  		if n == nil || len(n.List) == 0 {
   386  			return ""
   387  		}
   388  		if len(n.List) == 1 {
   389  			return pkg.oneLineField(n.List[0], depth)
   390  		}
   391  		return dotDotDot
   392  
   393  	case *ast.FuncLit:
   394  		return pkg.oneLineNodeDepth(n.Type, depth) + " { ... }"
   395  
   396  	case *ast.CompositeLit:
   397  		typ := pkg.oneLineNodeDepth(n.Type, depth)
   398  		if len(n.Elts) == 0 {
   399  			return fmt.Sprintf("%s{}", typ)
   400  		}
   401  		return fmt.Sprintf("%s{ %s }", typ, dotDotDot)
   402  
   403  	case *ast.ArrayType:
   404  		length := pkg.oneLineNodeDepth(n.Len, depth)
   405  		element := pkg.oneLineNodeDepth(n.Elt, depth)
   406  		return fmt.Sprintf("[%s]%s", length, element)
   407  
   408  	case *ast.MapType:
   409  		key := pkg.oneLineNodeDepth(n.Key, depth)
   410  		value := pkg.oneLineNodeDepth(n.Value, depth)
   411  		return fmt.Sprintf("map[%s]%s", key, value)
   412  
   413  	case *ast.CallExpr:
   414  		fnc := pkg.oneLineNodeDepth(n.Fun, depth)
   415  		var args []string
   416  		for _, arg := range n.Args {
   417  			args = append(args, pkg.oneLineNodeDepth(arg, depth))
   418  		}
   419  		return fmt.Sprintf("%s(%s)", fnc, joinStrings(args))
   420  
   421  	case *ast.UnaryExpr:
   422  		return fmt.Sprintf("%s%s", n.Op, pkg.oneLineNodeDepth(n.X, depth))
   423  
   424  	case *ast.Ident:
   425  		return n.Name
   426  
   427  	default:
   428  		// As a fallback, use default formatter for all unknown node types.
   429  		buf := new(strings.Builder)
   430  		format.Node(buf, pkg.fs, node)
   431  		s := buf.String()
   432  		if strings.Contains(s, "\n") {
   433  			return dotDotDot
   434  		}
   435  		return s
   436  	}
   437  }
   438  
   439  func (pkg *Package) formatTypeParams(list *ast.FieldList, depth int) string {
   440  	if list.NumFields() == 0 {
   441  		return ""
   442  	}
   443  	var tparams []string
   444  	for _, field := range list.List {
   445  		tparams = append(tparams, pkg.oneLineField(field, depth))
   446  	}
   447  	return "[" + joinStrings(tparams) + "]"
   448  }
   449  
   450  // oneLineField returns a one-line summary of the field.
   451  func (pkg *Package) oneLineField(field *ast.Field, depth int) string {
   452  	var names []string
   453  	for _, name := range field.Names {
   454  		names = append(names, name.Name)
   455  	}
   456  	if len(names) == 0 {
   457  		return pkg.oneLineNodeDepth(field.Type, depth)
   458  	}
   459  	return joinStrings(names) + " " + pkg.oneLineNodeDepth(field.Type, depth)
   460  }
   461  
   462  // joinStrings formats the input as a comma-separated list,
   463  // but truncates the list at some reasonable length if necessary.
   464  func joinStrings(ss []string) string {
   465  	var n int
   466  	for i, s := range ss {
   467  		n += len(s) + len(", ")
   468  		if n > punchedCardWidth {
   469  			ss = append(ss[:i:i], "...")
   470  			break
   471  		}
   472  	}
   473  	return strings.Join(ss, ", ")
   474  }
   475  
   476  // printHeader prints a header for the section named s, adding a blank line on each side.
   477  func (pkg *Package) printHeader(s string) {
   478  	pkg.Printf("\n%s\n\n", s)
   479  }
   480  
   481  // constsDoc prints all const documentation, if any, including a header.
   482  // The one argument is the valueDoc registry.
   483  func (pkg *Package) constsDoc(printed map[*ast.GenDecl]bool) {
   484  	var header bool
   485  	for _, value := range pkg.doc.Consts {
   486  		// Constants and variables come in groups, and valueDoc prints
   487  		// all the items in the group. We only need to find one exported symbol.
   488  		for _, name := range value.Names {
   489  			if isExported(name) && !pkg.typedValue[value] {
   490  				if !header {
   491  					pkg.printHeader("CONSTANTS")
   492  					header = true
   493  				}
   494  				pkg.valueDoc(value, printed)
   495  				break
   496  			}
   497  		}
   498  	}
   499  }
   500  
   501  // varsDoc prints all var documentation, if any, including a header.
   502  // Printed is the valueDoc registry.
   503  func (pkg *Package) varsDoc(printed map[*ast.GenDecl]bool) {
   504  	var header bool
   505  	for _, value := range pkg.doc.Vars {
   506  		// Constants and variables come in groups, and valueDoc prints
   507  		// all the items in the group. We only need to find one exported symbol.
   508  		for _, name := range value.Names {
   509  			if isExported(name) && !pkg.typedValue[value] {
   510  				if !header {
   511  					pkg.printHeader("VARIABLES")
   512  					header = true
   513  				}
   514  				pkg.valueDoc(value, printed)
   515  				break
   516  			}
   517  		}
   518  	}
   519  }
   520  
   521  // funcsDoc prints all func documentation, if any, including a header.
   522  func (pkg *Package) funcsDoc() {
   523  	var header bool
   524  	for _, fun := range pkg.doc.Funcs {
   525  		if isExported(fun.Name) && !pkg.constructor[fun] {
   526  			if !header {
   527  				pkg.printHeader("FUNCTIONS")
   528  				header = true
   529  			}
   530  			pkg.emit(fun.Doc, fun.Decl)
   531  		}
   532  	}
   533  }
   534  
   535  // typesDoc prints all type documentation, if any, including a header.
   536  func (pkg *Package) typesDoc() {
   537  	var header bool
   538  	for _, typ := range pkg.doc.Types {
   539  		if isExported(typ.Name) {
   540  			if !header {
   541  				pkg.printHeader("TYPES")
   542  				header = true
   543  			}
   544  			pkg.typeDoc(typ)
   545  		}
   546  	}
   547  }
   548  
   549  // packageDoc prints the docs for the package.
   550  func (pkg *Package) packageDoc() {
   551  	pkg.Printf("") // Trigger the package clause; we know the package exists.
   552  	if showAll || !short {
   553  		pkg.ToText(&pkg.buf, pkg.doc.Doc, "", indent)
   554  		pkg.newlines(1)
   555  	}
   556  
   557  	switch {
   558  	case showAll:
   559  		printed := make(map[*ast.GenDecl]bool) // valueDoc registry
   560  		pkg.constsDoc(printed)
   561  		pkg.varsDoc(printed)
   562  		pkg.funcsDoc()
   563  		pkg.typesDoc()
   564  
   565  	case pkg.pkg.Name == "main" && !showCmd:
   566  		// Show only package docs for commands.
   567  		return
   568  
   569  	default:
   570  		if !short {
   571  			pkg.newlines(2) // Guarantee blank line before the components.
   572  		}
   573  		pkg.valueSummary(pkg.doc.Consts, false)
   574  		pkg.valueSummary(pkg.doc.Vars, false)
   575  		pkg.funcSummary(pkg.doc.Funcs, false)
   576  		pkg.typeSummary()
   577  		pkg.exampleSummary(pkg.doc.Examples, false)
   578  	}
   579  
   580  	if !short {
   581  		pkg.bugs()
   582  	}
   583  }
   584  
   585  // packageClause prints the package clause.
   586  func (pkg *Package) packageClause() {
   587  	if short {
   588  		return
   589  	}
   590  	importPath := pkg.build.ImportComment
   591  	if importPath == "" {
   592  		importPath = pkg.build.ImportPath
   593  	}
   594  
   595  	// If we're using modules, the import path derived from module code locations wins.
   596  	// If we did a file system scan, we knew the import path when we found the directory.
   597  	// But if we started with a directory name, we never knew the import path.
   598  	// Either way, we don't know it now, and it's cheap to (re)compute it.
   599  	if usingModules {
   600  		for _, root := range codeRoots() {
   601  			if pkg.build.Dir == root.dir {
   602  				importPath = root.importPath
   603  				break
   604  			}
   605  			if strings.HasPrefix(pkg.build.Dir, root.dir+string(filepath.Separator)) {
   606  				suffix := filepath.ToSlash(pkg.build.Dir[len(root.dir)+1:])
   607  				if root.importPath == "" {
   608  					importPath = suffix
   609  				} else {
   610  					importPath = root.importPath + "/" + suffix
   611  				}
   612  				break
   613  			}
   614  		}
   615  	}
   616  
   617  	pkg.Printf("package %s // import %q\n\n", pkg.name, importPath)
   618  	if !usingModules && importPath != pkg.build.ImportPath {
   619  		pkg.Printf("WARNING: package source is installed in %q\n", pkg.build.ImportPath)
   620  	}
   621  }
   622  
   623  // valueSummary prints a one-line summary for each set of values and constants.
   624  // If all the types in a constant or variable declaration belong to the same
   625  // type they can be printed by typeSummary, and so can be suppressed here.
   626  func (pkg *Package) valueSummary(values []*doc.Value, showGrouped bool) {
   627  	var isGrouped map[*doc.Value]bool
   628  	if !showGrouped {
   629  		isGrouped = make(map[*doc.Value]bool)
   630  		for _, typ := range pkg.doc.Types {
   631  			if !isExported(typ.Name) {
   632  				continue
   633  			}
   634  			for _, c := range typ.Consts {
   635  				isGrouped[c] = true
   636  			}
   637  			for _, v := range typ.Vars {
   638  				isGrouped[v] = true
   639  			}
   640  		}
   641  	}
   642  
   643  	for _, value := range values {
   644  		if !isGrouped[value] {
   645  			if decl := pkg.oneLineNode(value.Decl); decl != "" {
   646  				pkg.Printf("%s\n", decl)
   647  			}
   648  		}
   649  	}
   650  }
   651  
   652  // funcSummary prints a one-line summary for each function. Constructors
   653  // are printed by typeSummary, below, and so can be suppressed here.
   654  func (pkg *Package) funcSummary(funcs []*doc.Func, showConstructors bool) {
   655  	for _, fun := range funcs {
   656  		// Exported functions only. The go/doc package does not include methods here.
   657  		if isExported(fun.Name) {
   658  			if showConstructors || !pkg.constructor[fun] {
   659  				pkg.Printf("%s\n", pkg.oneLineNode(fun.Decl))
   660  				if showEx {
   661  					pkg.exampleSummary(fun.Examples, false)
   662  				}
   663  			}
   664  		}
   665  	}
   666  }
   667  
   668  // exampleSummary prints a one-line summary for each example.
   669  func (pkg *Package) exampleSummary(exs []*doc.Example, showDoc bool) {
   670  	if !showEx {
   671  		return
   672  	}
   673  	for _, ex := range exs {
   674  		pkg.Printf(indent+"func Example%s()\n", ex.Name)
   675  		if showDoc && ex.Doc != "" {
   676  			pkg.ToText(&pkg.buf, ex.Doc, indent+indent, indent+indent)
   677  		}
   678  	}
   679  }
   680  
   681  // typeSummary prints a one-line summary for each type, followed by its constructors.
   682  func (pkg *Package) typeSummary() {
   683  	for _, typ := range pkg.doc.Types {
   684  		for _, spec := range typ.Decl.Specs {
   685  			typeSpec := spec.(*ast.TypeSpec) // Must succeed.
   686  			if isExported(typeSpec.Name.Name) {
   687  				pkg.Printf("%s\n", pkg.oneLineNode(typeSpec))
   688  				// Now print the consts, vars, and constructors.
   689  				for _, c := range typ.Consts {
   690  					if decl := pkg.oneLineNode(c.Decl); decl != "" {
   691  						pkg.Printf(indent+"%s\n", decl)
   692  					}
   693  				}
   694  				for _, v := range typ.Vars {
   695  					if decl := pkg.oneLineNode(v.Decl); decl != "" {
   696  						pkg.Printf(indent+"%s\n", decl)
   697  					}
   698  				}
   699  				for _, constructor := range typ.Funcs {
   700  					if isExported(constructor.Name) {
   701  						pkg.Printf(indent+"%s\n", pkg.oneLineNode(constructor.Decl))
   702  						pkg.exampleSummary(constructor.Examples, false)
   703  					}
   704  				}
   705  				pkg.exampleSummary(typ.Examples, false)
   706  			}
   707  		}
   708  	}
   709  }
   710  
   711  // bugs prints the BUGS information for the package.
   712  // TODO: Provide access to TODOs and NOTEs as well (very noisy so off by default)?
   713  func (pkg *Package) bugs() {
   714  	if pkg.doc.Notes["BUG"] == nil {
   715  		return
   716  	}
   717  	pkg.Printf("\n")
   718  	for _, note := range pkg.doc.Notes["BUG"] {
   719  		pkg.Printf("%s: %v\n", "BUG", note.Body)
   720  	}
   721  }
   722  
   723  // findValues finds the doc.Values that describe the symbol.
   724  func (pkg *Package) findValues(symbol string, docValues []*doc.Value) (values []*doc.Value) {
   725  	for _, value := range docValues {
   726  		for _, name := range value.Names {
   727  			if match(symbol, name) {
   728  				values = append(values, value)
   729  			}
   730  		}
   731  	}
   732  	return
   733  }
   734  
   735  // findFuncs finds the doc.Funcs that describes the symbol.
   736  func (pkg *Package) findFuncs(symbol string) (funcs []*doc.Func) {
   737  	for _, fun := range pkg.doc.Funcs {
   738  		if match(symbol, fun.Name) {
   739  			funcs = append(funcs, fun)
   740  		}
   741  	}
   742  	return
   743  }
   744  
   745  // findTypes finds the doc.Types that describes the symbol.
   746  // If symbol is empty, it finds all exported types.
   747  func (pkg *Package) findTypes(symbol string) (types []*doc.Type) {
   748  	for _, typ := range pkg.doc.Types {
   749  		if symbol == "" && isExported(typ.Name) || match(symbol, typ.Name) {
   750  			types = append(types, typ)
   751  		}
   752  	}
   753  	return
   754  }
   755  
   756  // findExamples finds any examples that describe the symbol.
   757  func (pkg *Package) findExamples(symbol string) (examples []*doc.Example) {
   758  	if !strings.HasPrefix(symbol, "Example") {
   759  		return
   760  	}
   761  	symbol = strings.TrimPrefix(symbol, "Example")
   762  	var all []*doc.Example
   763  	all = append(all, pkg.doc.Examples...)
   764  	for _, typ := range pkg.doc.Types {
   765  		all = append(all, typ.Examples...)
   766  		for _, fun := range typ.Funcs {
   767  			all = append(all, fun.Examples...)
   768  		}
   769  		for _, fun := range typ.Methods {
   770  			all = append(all, fun.Examples...)
   771  		}
   772  	}
   773  	for _, fun := range pkg.doc.Funcs {
   774  		all = append(all, fun.Examples...)
   775  	}
   776  
   777  	// always include unexported in below match(), so Example_one
   778  	// is still matched despite trimming the Example_ part.
   779  	u := unexported
   780  	unexported = true
   781  	for _, ex := range all {
   782  		if match(symbol, ex.Name) {
   783  			examples = append(examples, ex)
   784  		}
   785  	}
   786  	unexported = u
   787  	return
   788  }
   789  
   790  // findTypeSpec returns the ast.TypeSpec within the declaration that defines the symbol.
   791  // The name must match exactly.
   792  func (pkg *Package) findTypeSpec(decl *ast.GenDecl, symbol string) *ast.TypeSpec {
   793  	for _, spec := range decl.Specs {
   794  		typeSpec := spec.(*ast.TypeSpec) // Must succeed.
   795  		if symbol == typeSpec.Name.Name {
   796  			return typeSpec
   797  		}
   798  	}
   799  	return nil
   800  }
   801  
   802  // symbolDoc prints the docs for symbol. There may be multiple matches.
   803  // If symbol matches a type, output includes its methods factories and associated constants.
   804  // If there is no top-level symbol, symbolDoc looks for methods that match.
   805  func (pkg *Package) symbolDoc(symbol string) bool {
   806  	found := false
   807  	// Functions.
   808  	for _, fun := range pkg.findFuncs(symbol) {
   809  		// Symbol is a function.
   810  		decl := fun.Decl
   811  		pkg.emit(fun.Doc, decl)
   812  		pkg.exampleSummary(fun.Examples, true)
   813  		found = true
   814  	}
   815  	for _, ex := range pkg.findExamples(symbol) {
   816  		pkg.emitExample(ex)
   817  		found = true
   818  	}
   819  	// Constants and variables behave the same.
   820  	values := pkg.findValues(symbol, pkg.doc.Consts)
   821  	values = append(values, pkg.findValues(symbol, pkg.doc.Vars)...)
   822  	printed := make(map[*ast.GenDecl]bool) // valueDoc registry
   823  	for _, value := range values {
   824  		pkg.valueDoc(value, printed)
   825  		found = true
   826  	}
   827  	// Types.
   828  	for _, typ := range pkg.findTypes(symbol) {
   829  		pkg.typeDoc(typ)
   830  		found = true
   831  	}
   832  	if !found {
   833  		// See if there are methods.
   834  		if !pkg.printMethodDoc("", symbol) {
   835  			return false
   836  		}
   837  	}
   838  	return true
   839  }
   840  
   841  // valueDoc prints the docs for a constant or variable. The printed map records
   842  // which values have been printed already to avoid duplication. Otherwise, a
   843  // declaration like:
   844  //
   845  //	const ( c = 1; C = 2 )
   846  //
   847  // … could be printed twice if the -u flag is set, as it matches twice.
   848  func (pkg *Package) valueDoc(value *doc.Value, printed map[*ast.GenDecl]bool) {
   849  	if printed[value.Decl] {
   850  		return
   851  	}
   852  	// Print each spec only if there is at least one exported symbol in it.
   853  	// (See issue 11008.)
   854  	// TODO: Should we elide unexported symbols from a single spec?
   855  	// It's an unlikely scenario, probably not worth the trouble.
   856  	// TODO: Would be nice if go/doc did this for us.
   857  	specs := make([]ast.Spec, 0, len(value.Decl.Specs))
   858  	var typ ast.Expr
   859  	for _, spec := range value.Decl.Specs {
   860  		vspec := spec.(*ast.ValueSpec)
   861  
   862  		// The type name may carry over from a previous specification in the
   863  		// case of constants and iota.
   864  		if vspec.Type != nil {
   865  			typ = vspec.Type
   866  		}
   867  
   868  		for _, ident := range vspec.Names {
   869  			if showSrc || isExported(ident.Name) {
   870  				if vspec.Type == nil && vspec.Values == nil && typ != nil {
   871  					// This a standalone identifier, as in the case of iota usage.
   872  					// Thus, assume the type comes from the previous type.
   873  					vspec.Type = &ast.Ident{
   874  						Name:    pkg.oneLineNode(typ),
   875  						NamePos: vspec.End() - 1,
   876  					}
   877  				}
   878  
   879  				specs = append(specs, vspec)
   880  				typ = nil // Only inject type on first exported identifier
   881  				break
   882  			}
   883  		}
   884  	}
   885  	if len(specs) == 0 {
   886  		return
   887  	}
   888  	value.Decl.Specs = specs
   889  	pkg.emit(value.Doc, value.Decl)
   890  	printed[value.Decl] = true
   891  }
   892  
   893  // typeDoc prints the docs for a type, including constructors and other items
   894  // related to it.
   895  func (pkg *Package) typeDoc(typ *doc.Type) {
   896  	decl := typ.Decl
   897  	spec := pkg.findTypeSpec(decl, typ.Name)
   898  	trimUnexportedElems(spec)
   899  	// If there are multiple types defined, reduce to just this one.
   900  	if len(decl.Specs) > 1 {
   901  		decl.Specs = []ast.Spec{spec}
   902  	}
   903  	pkg.emit(typ.Doc, decl)
   904  	pkg.newlines(2)
   905  	// Show associated methods, constants, etc.
   906  	if showAll {
   907  		printed := make(map[*ast.GenDecl]bool) // valueDoc registry
   908  		// We can use append here to print consts, then vars. Ditto for funcs and methods.
   909  		values := typ.Consts
   910  		values = append(values, typ.Vars...)
   911  		for _, value := range values {
   912  			for _, name := range value.Names {
   913  				if isExported(name) {
   914  					pkg.valueDoc(value, printed)
   915  					break
   916  				}
   917  			}
   918  		}
   919  		funcs := typ.Funcs
   920  		funcs = append(funcs, typ.Methods...)
   921  		for _, fun := range funcs {
   922  			if isExported(fun.Name) {
   923  				pkg.emit(fun.Doc, fun.Decl)
   924  				if fun.Doc == "" {
   925  					pkg.newlines(2)
   926  				}
   927  			}
   928  		}
   929  	} else {
   930  		pkg.valueSummary(typ.Consts, true)
   931  		pkg.valueSummary(typ.Vars, true)
   932  		pkg.funcSummary(typ.Funcs, true)
   933  		pkg.exampleSummary(typ.Examples, false)
   934  		pkg.funcSummary(typ.Methods, true)
   935  	}
   936  }
   937  
   938  // trimUnexportedElems modifies spec in place to elide unexported fields from
   939  // structs and methods from interfaces (unless the unexported flag is set or we
   940  // are asked to show the original source).
   941  func trimUnexportedElems(spec *ast.TypeSpec) {
   942  	if showSrc {
   943  		return
   944  	}
   945  	switch typ := spec.Type.(type) {
   946  	case *ast.StructType:
   947  		typ.Fields = trimUnexportedFields(typ.Fields, false)
   948  	case *ast.InterfaceType:
   949  		typ.Methods = trimUnexportedFields(typ.Methods, true)
   950  	}
   951  }
   952  
   953  // trimUnexportedFields returns the field list trimmed of unexported fields.
   954  func trimUnexportedFields(fields *ast.FieldList, isInterface bool) *ast.FieldList {
   955  	what := "methods"
   956  	if !isInterface {
   957  		what = "fields"
   958  	}
   959  
   960  	trimmed := false
   961  	list := make([]*ast.Field, 0, len(fields.List))
   962  	for _, field := range fields.List {
   963  		// When printing fields we normally print field.Doc.
   964  		// Here we are going to pass the AST to go/format,
   965  		// which will print the comments from the AST,
   966  		// not field.Doc which is from go/doc.
   967  		// The two are similar but not identical;
   968  		// for example, field.Doc does not include directives.
   969  		// In order to consistently print field.Doc,
   970  		// we replace the comment in the AST with field.Doc.
   971  		// That will cause go/format to print what we want.
   972  		// See issue #56592.
   973  		if field.Doc != nil {
   974  			doc := field.Doc
   975  			text := doc.Text()
   976  
   977  			trailingBlankLine := len(doc.List[len(doc.List)-1].Text) == 2
   978  			if !trailingBlankLine {
   979  				// Remove trailing newline.
   980  				lt := len(text)
   981  				if lt > 0 && text[lt-1] == '\n' {
   982  					text = text[:lt-1]
   983  				}
   984  			}
   985  
   986  			start := doc.List[0].Slash
   987  			doc.List = doc.List[:0]
   988  			for line := range strings.SplitSeq(text, "\n") {
   989  				prefix := "// "
   990  				if len(line) > 0 && line[0] == '\t' {
   991  					prefix = "//"
   992  				}
   993  				doc.List = append(doc.List, &ast.Comment{
   994  					Text: prefix + line,
   995  				})
   996  			}
   997  			doc.List[0].Slash = start
   998  		}
   999  
  1000  		names := field.Names
  1001  		if len(names) == 0 {
  1002  			// Embedded type. Use the name of the type. It must be of the form ident or
  1003  			// pkg.ident (for structs and interfaces), or *ident or *pkg.ident (structs only).
  1004  			// Or a type embedded in a constraint.
  1005  			// Nothing else is allowed.
  1006  			ty := field.Type
  1007  			if se, ok := field.Type.(*ast.StarExpr); !isInterface && ok {
  1008  				// The form *ident or *pkg.ident is only valid on
  1009  				// embedded types in structs.
  1010  				ty = se.X
  1011  			}
  1012  			constraint := false
  1013  			switch ident := ty.(type) {
  1014  			case *ast.Ident:
  1015  				if isInterface && ident.Obj == nil &&
  1016  					(ident.Name == "error" || ident.Name == "comparable") {
  1017  					// For documentation purposes, we consider the builtin error
  1018  					// and comparable types special when embedded in an interface,
  1019  					// such that they always get shown publicly.
  1020  					list = append(list, field)
  1021  					continue
  1022  				}
  1023  				names = []*ast.Ident{ident}
  1024  			case *ast.SelectorExpr:
  1025  				// An embedded type may refer to a type in another package.
  1026  				names = []*ast.Ident{ident.Sel}
  1027  			default:
  1028  				// An approximation or union or type
  1029  				// literal in an interface.
  1030  				constraint = true
  1031  			}
  1032  			if names == nil && !constraint {
  1033  				// Can only happen if AST is incorrect. Safe to continue with a nil list.
  1034  				log.Print("invalid program: unexpected type for embedded field")
  1035  			}
  1036  		}
  1037  		// Trims if any is unexported. Good enough in practice.
  1038  		ok := true
  1039  		if !unexported {
  1040  			for _, name := range names {
  1041  				if !isExported(name.Name) {
  1042  					trimmed = true
  1043  					ok = false
  1044  					break
  1045  				}
  1046  			}
  1047  		}
  1048  		if ok {
  1049  			list = append(list, field)
  1050  		}
  1051  	}
  1052  	if !trimmed {
  1053  		return fields
  1054  	}
  1055  	unexportedField := &ast.Field{
  1056  		Type: &ast.Ident{
  1057  			// Hack: printer will treat this as a field with a named type.
  1058  			// Setting Name and NamePos to ("", fields.Closing-1) ensures that
  1059  			// when Pos and End are called on this field, they return the
  1060  			// position right before closing '}' character.
  1061  			Name:    "",
  1062  			NamePos: fields.Closing - 1,
  1063  		},
  1064  		Comment: &ast.CommentGroup{
  1065  			List: []*ast.Comment{{Text: fmt.Sprintf("// Has unexported %s.\n", what)}},
  1066  		},
  1067  	}
  1068  	return &ast.FieldList{
  1069  		Opening: fields.Opening,
  1070  		List:    append(list, unexportedField),
  1071  		Closing: fields.Closing,
  1072  	}
  1073  }
  1074  
  1075  // printMethodDoc prints the docs for matches of symbol.method.
  1076  // If symbol is empty, it prints all methods for any concrete type
  1077  // that match the name. It reports whether it found any methods.
  1078  func (pkg *Package) printMethodDoc(symbol, method string) bool {
  1079  	types := pkg.findTypes(symbol)
  1080  	if types == nil {
  1081  		if symbol == "" {
  1082  			return false
  1083  		}
  1084  		pkg.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath)
  1085  	}
  1086  	found := false
  1087  	for _, typ := range types {
  1088  		if len(typ.Methods) > 0 {
  1089  			for _, meth := range typ.Methods {
  1090  				if match(method, meth.Name) {
  1091  					decl := meth.Decl
  1092  					pkg.emit(meth.Doc, decl)
  1093  					pkg.exampleSummary(meth.Examples, true)
  1094  					found = true
  1095  				}
  1096  			}
  1097  			continue
  1098  		}
  1099  		if symbol == "" {
  1100  			continue
  1101  		}
  1102  		// Type may be an interface. The go/doc package does not attach
  1103  		// an interface's methods to the doc.Type. We need to dig around.
  1104  		spec := pkg.findTypeSpec(typ.Decl, typ.Name)
  1105  		inter, ok := spec.Type.(*ast.InterfaceType)
  1106  		if !ok {
  1107  			// Not an interface type.
  1108  			continue
  1109  		}
  1110  
  1111  		// Collect and print only the methods that match.
  1112  		var methods []*ast.Field
  1113  		for _, iMethod := range inter.Methods.List {
  1114  			// This is an interface, so there can be only one name.
  1115  			// TODO: Anonymous methods (embedding)
  1116  			if len(iMethod.Names) == 0 {
  1117  				continue
  1118  			}
  1119  			name := iMethod.Names[0].Name
  1120  			if match(method, name) {
  1121  				methods = append(methods, iMethod)
  1122  				found = true
  1123  			}
  1124  		}
  1125  		if found {
  1126  			pkg.Printf("type %s ", spec.Name)
  1127  			inter.Methods.List, methods = methods, inter.Methods.List
  1128  			err := format.Node(&pkg.buf, pkg.fs, inter)
  1129  			if err != nil {
  1130  				log.Fatal(err)
  1131  			}
  1132  			pkg.newlines(1)
  1133  			// Restore the original methods.
  1134  			inter.Methods.List = methods
  1135  		}
  1136  	}
  1137  	return found
  1138  }
  1139  
  1140  // printFieldDoc prints the docs for matches of symbol.fieldName.
  1141  // It reports whether it found any field.
  1142  // Both symbol and fieldName must be non-empty or it returns false.
  1143  func (pkg *Package) printFieldDoc(symbol, fieldName string) bool {
  1144  	if symbol == "" || fieldName == "" {
  1145  		return false
  1146  	}
  1147  	types := pkg.findTypes(symbol)
  1148  	if types == nil {
  1149  		pkg.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath)
  1150  	}
  1151  	found := false
  1152  	numUnmatched := 0
  1153  	for _, typ := range types {
  1154  		// Type must be a struct.
  1155  		spec := pkg.findTypeSpec(typ.Decl, typ.Name)
  1156  		structType, ok := spec.Type.(*ast.StructType)
  1157  		if !ok {
  1158  			// Not a struct type.
  1159  			continue
  1160  		}
  1161  		for _, field := range structType.Fields.List {
  1162  			// TODO: Anonymous fields.
  1163  			for _, name := range field.Names {
  1164  				if !match(fieldName, name.Name) {
  1165  					numUnmatched++
  1166  					continue
  1167  				}
  1168  				if !found {
  1169  					pkg.Printf("type %s struct {\n", typ.Name)
  1170  				}
  1171  				if field.Doc != nil {
  1172  					// To present indented blocks in comments correctly, process the comment as
  1173  					// a unit before adding the leading // to each line.
  1174  					docBuf := new(bytes.Buffer)
  1175  					pkg.ToText(docBuf, field.Doc.Text(), "", indent)
  1176  					scanner := bufio.NewScanner(docBuf)
  1177  					for scanner.Scan() {
  1178  						fmt.Fprintf(&pkg.buf, "%s// %s\n", indent, scanner.Bytes())
  1179  					}
  1180  				}
  1181  				s := pkg.oneLineNode(field.Type)
  1182  				lineComment := ""
  1183  				if field.Comment != nil {
  1184  					lineComment = fmt.Sprintf("  %s", field.Comment.List[0].Text)
  1185  				}
  1186  				pkg.Printf("%s%s %s%s\n", indent, name, s, lineComment)
  1187  				found = true
  1188  			}
  1189  		}
  1190  	}
  1191  	if found {
  1192  		if numUnmatched > 0 {
  1193  			pkg.Printf("\n    // ... other fields elided ...\n")
  1194  		}
  1195  		pkg.Printf("}\n")
  1196  	}
  1197  	return found
  1198  }
  1199  
  1200  // match reports whether the user's symbol matches the program's.
  1201  // A lower-case character in the user's string matches either case in the program's.
  1202  // The program string must be exported.
  1203  func match(user, program string) bool {
  1204  	if !isExported(program) {
  1205  		return false
  1206  	}
  1207  	if matchCase {
  1208  		return user == program
  1209  	}
  1210  	for _, u := range user {
  1211  		p, w := utf8.DecodeRuneInString(program)
  1212  		program = program[w:]
  1213  		if u == p {
  1214  			continue
  1215  		}
  1216  		if unicode.IsLower(u) && simpleFold(u) == simpleFold(p) {
  1217  			continue
  1218  		}
  1219  		return false
  1220  	}
  1221  	return program == ""
  1222  }
  1223  
  1224  // simpleFold returns the minimum rune equivalent to r
  1225  // under Unicode-defined simple case folding.
  1226  func simpleFold(r rune) rune {
  1227  	for {
  1228  		r1 := unicode.SimpleFold(r)
  1229  		if r1 <= r {
  1230  			return r1 // wrapped around, found min
  1231  		}
  1232  		r = r1
  1233  	}
  1234  }
  1235  
  1236  // emitExample prints an example and its output.
  1237  func (pkg *Package) emitExample(ex *doc.Example) {
  1238  	pkg.buf.printed = true // omit the package clause
  1239  	var err error
  1240  	if ex.Play != nil {
  1241  		err = format.Node(&pkg.buf, pkg.fs, ex.Play)
  1242  	} else {
  1243  		// If code is an *ast.BlockStmt, trim the braces and indentation
  1244  		// by just printing the enclosed List of ast.[]Stmt.
  1245  		b, ok := ex.Code.(*ast.BlockStmt)
  1246  		if !ok {
  1247  			err = format.Node(&pkg.buf, pkg.fs, ex.Code)
  1248  		} else {
  1249  			err = format.Node(&pkg.buf, pkg.fs, b.List)
  1250  		}
  1251  	}
  1252  	if err != nil {
  1253  		log.Fatal(err)
  1254  	}
  1255  	if ex.Output != "" {
  1256  		pkg.newlines(2)
  1257  		pkg.Printf("Output: ")
  1258  		if strings.Count(ex.Output, "\n") > 1 {
  1259  			pkg.newlines(1)
  1260  		}
  1261  		pkg.Printf("%s", ex.Output)
  1262  	}
  1263  	pkg.newlines(1)
  1264  }
  1265  

View as plain text