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