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