github.com/gocuntian/go@v0.0.0-20160610041250-fee02d270bf8/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 }