github.com/mh-cbon/go@v0.0.0-20160603070303-9e112a3fe4c0/src/cmd/doc/pkg.go (about)

     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 main
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"go/ast"
    11  	"go/build"
    12  	"go/doc"
    13  	"go/format"
    14  	"go/parser"
    15  	"go/token"
    16  	"io"
    17  	"log"
    18  	"os"
    19  	"path/filepath"
    20  	"strings"
    21  	"unicode"
    22  	"unicode/utf8"
    23  )
    24  
    25  const (
    26  	punchedCardWidth = 80 // These things just won't leave us alone.
    27  	indentedWidth    = punchedCardWidth - len(indent)
    28  	indent           = "    "
    29  )
    30  
    31  type Package struct {
    32  	writer   io.Writer    // Destination for output.
    33  	name     string       // Package name, json for encoding/json.
    34  	userPath string       // String the user used to find this package.
    35  	pkg      *ast.Package // Parsed package.
    36  	file     *ast.File    // Merged from all files in the package
    37  	doc      *doc.Package
    38  	build    *build.Package
    39  	fs       *token.FileSet // Needed for printing.
    40  	buf      bytes.Buffer
    41  }
    42  
    43  type PackageError string // type returned by pkg.Fatalf.
    44  
    45  func (p PackageError) Error() string {
    46  	return string(p)
    47  }
    48  
    49  // prettyPath returns a version of the package path that is suitable for an
    50  // error message. It obeys the import comment if present. Also, since
    51  // pkg.build.ImportPath is sometimes the unhelpful "" or ".", it looks for a
    52  // directory name in GOROOT or GOPATH if that happens.
    53  func (pkg *Package) prettyPath() string {
    54  	path := pkg.build.ImportComment
    55  	if path == "" {
    56  		path = pkg.build.ImportPath
    57  	}
    58  	if path != "." && path != "" {
    59  		return path
    60  	}
    61  	// Convert the source directory into a more useful path.
    62  	// Also convert everything to slash-separated paths for uniform handling.
    63  	path = filepath.Clean(filepath.ToSlash(pkg.build.Dir))
    64  	// Can we find a decent prefix?
    65  	goroot := filepath.Join(build.Default.GOROOT, "src")
    66  	if p, ok := trim(path, filepath.ToSlash(goroot)); ok {
    67  		return p
    68  	}
    69  	for _, gopath := range splitGopath() {
    70  		if p, ok := trim(path, filepath.ToSlash(gopath)); ok {
    71  			return p
    72  		}
    73  	}
    74  	return path
    75  }
    76  
    77  // trim trims the directory prefix from the path, paying attention
    78  // to the path separator. If they are the same string or the prefix
    79  // is not present the original is returned. The boolean reports whether
    80  // the prefix is present. That path and prefix have slashes for separators.
    81  func trim(path, prefix string) (string, bool) {
    82  	if !strings.HasPrefix(path, prefix) {
    83  		return path, false
    84  	}
    85  	if path == prefix {
    86  		return path, true
    87  	}
    88  	if path[len(prefix)] == '/' {
    89  		return path[len(prefix)+1:], true
    90  	}
    91  	return path, false // Textual prefix but not a path prefix.
    92  }
    93  
    94  // pkg.Fatalf is like log.Fatalf, but panics so it can be recovered in the
    95  // main do function, so it doesn't cause an exit. Allows testing to work
    96  // without running a subprocess. The log prefix will be added when
    97  // logged in main; it is not added here.
    98  func (pkg *Package) Fatalf(format string, args ...interface{}) {
    99  	panic(PackageError(fmt.Sprintf(format, args...)))
   100  }
   101  
   102  // parsePackage turns the build package we found into a parsed package
   103  // we can then use to generate documentation.
   104  func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Package {
   105  	fs := token.NewFileSet()
   106  	// include tells parser.ParseDir which files to include.
   107  	// That means the file must be in the build package's GoFiles or CgoFiles
   108  	// list only (no tag-ignored files, tests, swig or other non-Go files).
   109  	include := func(info os.FileInfo) bool {
   110  		for _, name := range pkg.GoFiles {
   111  			if name == info.Name() {
   112  				return true
   113  			}
   114  		}
   115  		for _, name := range pkg.CgoFiles {
   116  			if name == info.Name() {
   117  				return true
   118  			}
   119  		}
   120  		return false
   121  	}
   122  	pkgs, err := parser.ParseDir(fs, pkg.Dir, include, parser.ParseComments)
   123  	if err != nil {
   124  		log.Fatal(err)
   125  	}
   126  	// Make sure they are all in one package.
   127  	if len(pkgs) != 1 {
   128  		log.Fatalf("multiple packages in directory %s", pkg.Dir)
   129  	}
   130  	astPkg := pkgs[pkg.Name]
   131  
   132  	// TODO: go/doc does not include typed constants in the constants
   133  	// list, which is what we want. For instance, time.Sunday is of type
   134  	// time.Weekday, so it is defined in the type but not in the
   135  	// Consts list for the package. This prevents
   136  	//	go doc time.Sunday
   137  	// from finding the symbol. Work around this for now, but we
   138  	// should fix it in go/doc.
   139  	// A similar story applies to factory functions.
   140  	docPkg := doc.New(astPkg, pkg.ImportPath, doc.AllDecls)
   141  	for _, typ := range docPkg.Types {
   142  		docPkg.Consts = append(docPkg.Consts, typ.Consts...)
   143  		docPkg.Vars = append(docPkg.Vars, typ.Vars...)
   144  		docPkg.Funcs = append(docPkg.Funcs, typ.Funcs...)
   145  	}
   146  
   147  	return &Package{
   148  		writer:   writer,
   149  		name:     pkg.Name,
   150  		userPath: userPath,
   151  		pkg:      astPkg,
   152  		file:     ast.MergePackageFiles(astPkg, 0),
   153  		doc:      docPkg,
   154  		build:    pkg,
   155  		fs:       fs,
   156  	}
   157  }
   158  
   159  func (pkg *Package) Printf(format string, args ...interface{}) {
   160  	fmt.Fprintf(&pkg.buf, format, args...)
   161  }
   162  
   163  func (pkg *Package) flush() {
   164  	_, err := pkg.writer.Write(pkg.buf.Bytes())
   165  	if err != nil {
   166  		log.Fatal(err)
   167  	}
   168  	pkg.buf.Reset() // Not needed, but it's a flush.
   169  }
   170  
   171  var newlineBytes = []byte("\n\n") // We never ask for more than 2.
   172  
   173  // newlines guarantees there are n newlines at the end of the buffer.
   174  func (pkg *Package) newlines(n int) {
   175  	for !bytes.HasSuffix(pkg.buf.Bytes(), newlineBytes[:n]) {
   176  		pkg.buf.WriteRune('\n')
   177  	}
   178  }
   179  
   180  // emit prints the node.
   181  func (pkg *Package) emit(comment string, node ast.Node) {
   182  	if node != nil {
   183  		err := format.Node(&pkg.buf, pkg.fs, node)
   184  		if err != nil {
   185  			log.Fatal(err)
   186  		}
   187  		if comment != "" {
   188  			pkg.newlines(1)
   189  			doc.ToText(&pkg.buf, comment, "    ", indent, indentedWidth)
   190  			pkg.newlines(2) // Blank line after comment to separate from next item.
   191  		} else {
   192  			pkg.newlines(1)
   193  		}
   194  	}
   195  }
   196  
   197  var formatBuf bytes.Buffer // Reusable to avoid allocation.
   198  
   199  // formatNode is a helper function for printing.
   200  func (pkg *Package) formatNode(node ast.Node) []byte {
   201  	formatBuf.Reset()
   202  	format.Node(&formatBuf, pkg.fs, node)
   203  	return formatBuf.Bytes()
   204  }
   205  
   206  // oneLineFunc prints a function declaration as a single line.
   207  func (pkg *Package) oneLineFunc(decl *ast.FuncDecl) {
   208  	decl.Doc = nil
   209  	decl.Body = nil
   210  	pkg.emit("", decl)
   211  }
   212  
   213  // oneLineValueGenDecl prints a var or const declaration as a single line.
   214  func (pkg *Package) oneLineValueGenDecl(decl *ast.GenDecl) {
   215  	decl.Doc = nil
   216  	dotDotDot := ""
   217  	if len(decl.Specs) > 1 {
   218  		dotDotDot = " ..."
   219  	}
   220  	// Find the first relevant spec.
   221  	for i, spec := range decl.Specs {
   222  		valueSpec := spec.(*ast.ValueSpec) // Must succeed; we can't mix types in one genDecl.
   223  		if !isExported(valueSpec.Names[0].Name) {
   224  			continue
   225  		}
   226  		typ := ""
   227  		if valueSpec.Type != nil {
   228  			typ = fmt.Sprintf(" %s", pkg.formatNode(valueSpec.Type))
   229  		}
   230  		val := ""
   231  		if i < len(valueSpec.Values) && valueSpec.Values[i] != nil {
   232  			val = fmt.Sprintf(" = %s", pkg.formatNode(valueSpec.Values[i]))
   233  		}
   234  		pkg.Printf("%s %s%s%s%s\n", decl.Tok, valueSpec.Names[0], typ, val, dotDotDot)
   235  		break
   236  	}
   237  }
   238  
   239  // oneLineTypeDecl prints a type declaration as a single line.
   240  func (pkg *Package) oneLineTypeDecl(spec *ast.TypeSpec) {
   241  	spec.Doc = nil
   242  	spec.Comment = nil
   243  	switch spec.Type.(type) {
   244  	case *ast.InterfaceType:
   245  		pkg.Printf("type %s interface { ... }\n", spec.Name)
   246  	case *ast.StructType:
   247  		pkg.Printf("type %s struct { ... }\n", spec.Name)
   248  	default:
   249  		pkg.Printf("type %s %s\n", spec.Name, pkg.formatNode(spec.Type))
   250  	}
   251  }
   252  
   253  // packageDoc prints the docs for the package (package doc plus one-liners of the rest).
   254  func (pkg *Package) packageDoc() {
   255  	defer pkg.flush()
   256  	if pkg.showInternals() {
   257  		pkg.packageClause(false)
   258  	}
   259  
   260  	doc.ToText(&pkg.buf, pkg.doc.Doc, "", indent, indentedWidth)
   261  	pkg.newlines(1)
   262  
   263  	if !pkg.showInternals() {
   264  		// Show only package docs for commands.
   265  		return
   266  	}
   267  
   268  	pkg.newlines(2) // Guarantee blank line before the components.
   269  	pkg.valueSummary(pkg.doc.Consts)
   270  	pkg.valueSummary(pkg.doc.Vars)
   271  	pkg.funcSummary(pkg.doc.Funcs, false)
   272  	pkg.typeSummary()
   273  	pkg.bugs()
   274  }
   275  
   276  // showInternals reports whether we should show the internals
   277  // of a package as opposed to just the package docs.
   278  // Used to decide whether to suppress internals for commands.
   279  // Called only by Package.packageDoc.
   280  func (pkg *Package) showInternals() bool {
   281  	return pkg.pkg.Name != "main" || showCmd
   282  }
   283  
   284  // packageClause prints the package clause.
   285  // The argument boolean, if true, suppresses the output if the
   286  // user's argument is identical to the actual package path or
   287  // is empty, meaning it's the current directory.
   288  func (pkg *Package) packageClause(checkUserPath bool) {
   289  	if checkUserPath {
   290  		if pkg.userPath == "" || pkg.userPath == pkg.build.ImportPath {
   291  			return
   292  		}
   293  	}
   294  	importPath := pkg.build.ImportComment
   295  	if importPath == "" {
   296  		importPath = pkg.build.ImportPath
   297  	}
   298  	pkg.Printf("package %s // import %q\n\n", pkg.name, importPath)
   299  	if importPath != pkg.build.ImportPath {
   300  		pkg.Printf("WARNING: package source is installed in %q\n", pkg.build.ImportPath)
   301  	}
   302  }
   303  
   304  // valueSummary prints a one-line summary for each set of values and constants.
   305  func (pkg *Package) valueSummary(values []*doc.Value) {
   306  	for _, value := range values {
   307  		pkg.oneLineValueGenDecl(value.Decl)
   308  	}
   309  }
   310  
   311  // funcSummary prints a one-line summary for each function. Constructors
   312  // are printed by typeSummary, below, and so can be suppressed here.
   313  func (pkg *Package) funcSummary(funcs []*doc.Func, showConstructors bool) {
   314  	// First, identify the constructors. Don't bother figuring out if they're exported.
   315  	var isConstructor map[*doc.Func]bool
   316  	if !showConstructors {
   317  		isConstructor = make(map[*doc.Func]bool)
   318  		for _, typ := range pkg.doc.Types {
   319  			for _, constructor := range typ.Funcs {
   320  				isConstructor[constructor] = true
   321  			}
   322  		}
   323  	}
   324  	for _, fun := range funcs {
   325  		decl := fun.Decl
   326  		// Exported functions only. The go/doc package does not include methods here.
   327  		if isExported(fun.Name) {
   328  			if !isConstructor[fun] {
   329  				pkg.oneLineFunc(decl)
   330  			}
   331  		}
   332  	}
   333  }
   334  
   335  // typeSummary prints a one-line summary for each type, followed by its constructors.
   336  func (pkg *Package) typeSummary() {
   337  	for _, typ := range pkg.doc.Types {
   338  		for _, spec := range typ.Decl.Specs {
   339  			typeSpec := spec.(*ast.TypeSpec) // Must succeed.
   340  			if isExported(typeSpec.Name.Name) {
   341  				pkg.oneLineTypeDecl(typeSpec)
   342  				// Now print the constructors.
   343  				for _, constructor := range typ.Funcs {
   344  					if isExported(constructor.Name) {
   345  						pkg.Printf(indent)
   346  						pkg.oneLineFunc(constructor.Decl)
   347  					}
   348  				}
   349  			}
   350  		}
   351  	}
   352  }
   353  
   354  // bugs prints the BUGS information for the package.
   355  // TODO: Provide access to TODOs and NOTEs as well (very noisy so off by default)?
   356  func (pkg *Package) bugs() {
   357  	if pkg.doc.Notes["BUG"] == nil {
   358  		return
   359  	}
   360  	pkg.Printf("\n")
   361  	for _, note := range pkg.doc.Notes["BUG"] {
   362  		pkg.Printf("%s: %v\n", "BUG", note.Body)
   363  	}
   364  }
   365  
   366  // findValues finds the doc.Values that describe the symbol.
   367  func (pkg *Package) findValues(symbol string, docValues []*doc.Value) (values []*doc.Value) {
   368  	for _, value := range docValues {
   369  		for _, name := range value.Names {
   370  			if match(symbol, name) {
   371  				values = append(values, value)
   372  			}
   373  		}
   374  	}
   375  	return
   376  }
   377  
   378  // findFuncs finds the doc.Funcs that describes the symbol.
   379  func (pkg *Package) findFuncs(symbol string) (funcs []*doc.Func) {
   380  	for _, fun := range pkg.doc.Funcs {
   381  		if match(symbol, fun.Name) {
   382  			funcs = append(funcs, fun)
   383  		}
   384  	}
   385  	return
   386  }
   387  
   388  // findTypes finds the doc.Types that describes the symbol.
   389  // If symbol is empty, it finds all exported types.
   390  func (pkg *Package) findTypes(symbol string) (types []*doc.Type) {
   391  	for _, typ := range pkg.doc.Types {
   392  		if symbol == "" && isExported(typ.Name) || match(symbol, typ.Name) {
   393  			types = append(types, typ)
   394  		}
   395  	}
   396  	return
   397  }
   398  
   399  // findTypeSpec returns the ast.TypeSpec within the declaration that defines the symbol.
   400  // The name must match exactly.
   401  func (pkg *Package) findTypeSpec(decl *ast.GenDecl, symbol string) *ast.TypeSpec {
   402  	for _, spec := range decl.Specs {
   403  		typeSpec := spec.(*ast.TypeSpec) // Must succeed.
   404  		if symbol == typeSpec.Name.Name {
   405  			return typeSpec
   406  		}
   407  	}
   408  	return nil
   409  }
   410  
   411  // symbolDoc prints the docs for symbol. There may be multiple matches.
   412  // If symbol matches a type, output includes its methods factories and associated constants.
   413  // If there is no top-level symbol, symbolDoc looks for methods that match.
   414  func (pkg *Package) symbolDoc(symbol string) bool {
   415  	defer pkg.flush()
   416  	found := false
   417  	// Functions.
   418  	for _, fun := range pkg.findFuncs(symbol) {
   419  		if !found {
   420  			pkg.packageClause(true)
   421  		}
   422  		// Symbol is a function.
   423  		decl := fun.Decl
   424  		decl.Body = nil
   425  		pkg.emit(fun.Doc, decl)
   426  		found = true
   427  	}
   428  	// Constants and variables behave the same.
   429  	values := pkg.findValues(symbol, pkg.doc.Consts)
   430  	values = append(values, pkg.findValues(symbol, pkg.doc.Vars)...)
   431  	for _, value := range values {
   432  		// Print each spec only if there is at least one exported symbol in it.
   433  		// (See issue 11008.)
   434  		// TODO: Should we elide unexported symbols from a single spec?
   435  		// It's an unlikely scenario, probably not worth the trouble.
   436  		// TODO: Would be nice if go/doc did this for us.
   437  		specs := make([]ast.Spec, 0, len(value.Decl.Specs))
   438  		for _, spec := range value.Decl.Specs {
   439  			vspec := spec.(*ast.ValueSpec)
   440  			for _, ident := range vspec.Names {
   441  				if isExported(ident.Name) {
   442  					specs = append(specs, vspec)
   443  					break
   444  				}
   445  			}
   446  		}
   447  		if len(specs) == 0 {
   448  			continue
   449  		}
   450  		value.Decl.Specs = specs
   451  		if !found {
   452  			pkg.packageClause(true)
   453  		}
   454  		pkg.emit(value.Doc, value.Decl)
   455  		found = true
   456  	}
   457  	// Types.
   458  	for _, typ := range pkg.findTypes(symbol) {
   459  		if !found {
   460  			pkg.packageClause(true)
   461  		}
   462  		decl := typ.Decl
   463  		spec := pkg.findTypeSpec(decl, typ.Name)
   464  		trimUnexportedElems(spec)
   465  		// If there are multiple types defined, reduce to just this one.
   466  		if len(decl.Specs) > 1 {
   467  			decl.Specs = []ast.Spec{spec}
   468  		}
   469  		pkg.emit(typ.Doc, decl)
   470  		// Show associated methods, constants, etc.
   471  		if len(typ.Consts) > 0 || len(typ.Vars) > 0 || len(typ.Funcs) > 0 || len(typ.Methods) > 0 {
   472  			pkg.Printf("\n")
   473  		}
   474  		pkg.valueSummary(typ.Consts)
   475  		pkg.valueSummary(typ.Vars)
   476  		pkg.funcSummary(typ.Funcs, true)
   477  		pkg.funcSummary(typ.Methods, true)
   478  		found = true
   479  	}
   480  	if !found {
   481  		// See if there are methods.
   482  		if !pkg.printMethodDoc("", symbol) {
   483  			return false
   484  		}
   485  	}
   486  	return true
   487  }
   488  
   489  // trimUnexportedElems modifies spec in place to elide unexported fields from
   490  // structs and methods from interfaces (unless the unexported flag is set).
   491  func trimUnexportedElems(spec *ast.TypeSpec) {
   492  	if unexported {
   493  		return
   494  	}
   495  	switch typ := spec.Type.(type) {
   496  	case *ast.StructType:
   497  		typ.Fields = trimUnexportedFields(typ.Fields, "fields")
   498  	case *ast.InterfaceType:
   499  		typ.Methods = trimUnexportedFields(typ.Methods, "methods")
   500  	}
   501  }
   502  
   503  // trimUnexportedFields returns the field list trimmed of unexported fields.
   504  func trimUnexportedFields(fields *ast.FieldList, what string) *ast.FieldList {
   505  	trimmed := false
   506  	list := make([]*ast.Field, 0, len(fields.List))
   507  	for _, field := range fields.List {
   508  		names := field.Names
   509  		if len(names) == 0 {
   510  			// Embedded type. Use the name of the type. It must be of type ident or *ident.
   511  			// Nothing else is allowed.
   512  			switch ident := field.Type.(type) {
   513  			case *ast.Ident:
   514  				names = []*ast.Ident{ident}
   515  			case *ast.StarExpr:
   516  				// Must have the form *identifier.
   517  				if ident, ok := ident.X.(*ast.Ident); ok {
   518  					names = []*ast.Ident{ident}
   519  				}
   520  			}
   521  			if names == nil {
   522  				// Can only happen if AST is incorrect. Safe to continue with a nil list.
   523  				log.Print("invalid program: unexpected type for embedded field")
   524  			}
   525  		}
   526  		// Trims if any is unexported. Good enough in practice.
   527  		ok := true
   528  		for _, name := range names {
   529  			if !isExported(name.Name) {
   530  				trimmed = true
   531  				ok = false
   532  				break
   533  			}
   534  		}
   535  		if ok {
   536  			list = append(list, field)
   537  		}
   538  	}
   539  	if !trimmed {
   540  		return fields
   541  	}
   542  	unexportedField := &ast.Field{
   543  		Type: &ast.Ident{
   544  			// Hack: printer will treat this as a field with a named type.
   545  			// Setting Name and NamePos to ("", fields.Closing-1) ensures that
   546  			// when Pos and End are called on this field, they return the
   547  			// position right before closing '}' character.
   548  			Name:    "",
   549  			NamePos: fields.Closing - 1,
   550  		},
   551  		Comment: &ast.CommentGroup{
   552  			List: []*ast.Comment{{Text: fmt.Sprintf("// Has unexported %s.\n", what)}},
   553  		},
   554  	}
   555  	return &ast.FieldList{
   556  		Opening: fields.Opening,
   557  		List:    append(list, unexportedField),
   558  		Closing: fields.Closing,
   559  	}
   560  }
   561  
   562  // printMethodDoc prints the docs for matches of symbol.method.
   563  // If symbol is empty, it prints all methods that match the name.
   564  // It reports whether it found any methods.
   565  func (pkg *Package) printMethodDoc(symbol, method string) bool {
   566  	defer pkg.flush()
   567  	types := pkg.findTypes(symbol)
   568  	if types == nil {
   569  		if symbol == "" {
   570  			return false
   571  		}
   572  		pkg.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath)
   573  	}
   574  	found := false
   575  	for _, typ := range types {
   576  		for _, meth := range typ.Methods {
   577  			if match(method, meth.Name) {
   578  				decl := meth.Decl
   579  				decl.Body = nil
   580  				pkg.emit(meth.Doc, decl)
   581  				found = true
   582  			}
   583  		}
   584  	}
   585  	return found
   586  }
   587  
   588  // methodDoc prints the docs for matches of symbol.method.
   589  func (pkg *Package) methodDoc(symbol, method string) bool {
   590  	defer pkg.flush()
   591  	return pkg.printMethodDoc(symbol, method)
   592  }
   593  
   594  // match reports whether the user's symbol matches the program's.
   595  // A lower-case character in the user's string matches either case in the program's.
   596  // The program string must be exported.
   597  func match(user, program string) bool {
   598  	if !isExported(program) {
   599  		return false
   600  	}
   601  	if matchCase {
   602  		return user == program
   603  	}
   604  	for _, u := range user {
   605  		p, w := utf8.DecodeRuneInString(program)
   606  		program = program[w:]
   607  		if u == p {
   608  			continue
   609  		}
   610  		if unicode.IsLower(u) && simpleFold(u) == simpleFold(p) {
   611  			continue
   612  		}
   613  		return false
   614  	}
   615  	return program == ""
   616  }
   617  
   618  // simpleFold returns the minimum rune equivalent to r
   619  // under Unicode-defined simple case folding.
   620  func simpleFold(r rune) rune {
   621  	for {
   622  		r1 := unicode.SimpleFold(r)
   623  		if r1 <= r {
   624  			return r1 // wrapped around, found min
   625  		}
   626  		r = r1
   627  	}
   628  }