github.com/liujq9674git/golang-src-1.7@v0.0.0-20230517174348-17f6ec47f3f8/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 if isExported(typ.Name) { 321 isConstructor[constructor] = true 322 } 323 } 324 } 325 } 326 for _, fun := range funcs { 327 decl := fun.Decl 328 // Exported functions only. The go/doc package does not include methods here. 329 if isExported(fun.Name) { 330 if !isConstructor[fun] { 331 pkg.oneLineFunc(decl) 332 } 333 } 334 } 335 } 336 337 // typeSummary prints a one-line summary for each type, followed by its constructors. 338 func (pkg *Package) typeSummary() { 339 for _, typ := range pkg.doc.Types { 340 for _, spec := range typ.Decl.Specs { 341 typeSpec := spec.(*ast.TypeSpec) // Must succeed. 342 if isExported(typeSpec.Name.Name) { 343 pkg.oneLineTypeDecl(typeSpec) 344 // Now print the constructors. 345 for _, constructor := range typ.Funcs { 346 if isExported(constructor.Name) { 347 pkg.Printf(indent) 348 pkg.oneLineFunc(constructor.Decl) 349 } 350 } 351 } 352 } 353 } 354 } 355 356 // bugs prints the BUGS information for the package. 357 // TODO: Provide access to TODOs and NOTEs as well (very noisy so off by default)? 358 func (pkg *Package) bugs() { 359 if pkg.doc.Notes["BUG"] == nil { 360 return 361 } 362 pkg.Printf("\n") 363 for _, note := range pkg.doc.Notes["BUG"] { 364 pkg.Printf("%s: %v\n", "BUG", note.Body) 365 } 366 } 367 368 // findValues finds the doc.Values that describe the symbol. 369 func (pkg *Package) findValues(symbol string, docValues []*doc.Value) (values []*doc.Value) { 370 for _, value := range docValues { 371 for _, name := range value.Names { 372 if match(symbol, name) { 373 values = append(values, value) 374 } 375 } 376 } 377 return 378 } 379 380 // findFuncs finds the doc.Funcs that describes the symbol. 381 func (pkg *Package) findFuncs(symbol string) (funcs []*doc.Func) { 382 for _, fun := range pkg.doc.Funcs { 383 if match(symbol, fun.Name) { 384 funcs = append(funcs, fun) 385 } 386 } 387 return 388 } 389 390 // findTypes finds the doc.Types that describes the symbol. 391 // If symbol is empty, it finds all exported types. 392 func (pkg *Package) findTypes(symbol string) (types []*doc.Type) { 393 for _, typ := range pkg.doc.Types { 394 if symbol == "" && isExported(typ.Name) || match(symbol, typ.Name) { 395 types = append(types, typ) 396 } 397 } 398 return 399 } 400 401 // findTypeSpec returns the ast.TypeSpec within the declaration that defines the symbol. 402 // The name must match exactly. 403 func (pkg *Package) findTypeSpec(decl *ast.GenDecl, symbol string) *ast.TypeSpec { 404 for _, spec := range decl.Specs { 405 typeSpec := spec.(*ast.TypeSpec) // Must succeed. 406 if symbol == typeSpec.Name.Name { 407 return typeSpec 408 } 409 } 410 return nil 411 } 412 413 // symbolDoc prints the docs for symbol. There may be multiple matches. 414 // If symbol matches a type, output includes its methods factories and associated constants. 415 // If there is no top-level symbol, symbolDoc looks for methods that match. 416 func (pkg *Package) symbolDoc(symbol string) bool { 417 defer pkg.flush() 418 found := false 419 // Functions. 420 for _, fun := range pkg.findFuncs(symbol) { 421 if !found { 422 pkg.packageClause(true) 423 } 424 // Symbol is a function. 425 decl := fun.Decl 426 decl.Body = nil 427 pkg.emit(fun.Doc, decl) 428 found = true 429 } 430 // Constants and variables behave the same. 431 values := pkg.findValues(symbol, pkg.doc.Consts) 432 values = append(values, pkg.findValues(symbol, pkg.doc.Vars)...) 433 for _, value := range values { 434 // Print each spec only if there is at least one exported symbol in it. 435 // (See issue 11008.) 436 // TODO: Should we elide unexported symbols from a single spec? 437 // It's an unlikely scenario, probably not worth the trouble. 438 // TODO: Would be nice if go/doc did this for us. 439 specs := make([]ast.Spec, 0, len(value.Decl.Specs)) 440 for _, spec := range value.Decl.Specs { 441 vspec := spec.(*ast.ValueSpec) 442 for _, ident := range vspec.Names { 443 if isExported(ident.Name) { 444 specs = append(specs, vspec) 445 break 446 } 447 } 448 } 449 if len(specs) == 0 { 450 continue 451 } 452 value.Decl.Specs = specs 453 if !found { 454 pkg.packageClause(true) 455 } 456 pkg.emit(value.Doc, value.Decl) 457 found = true 458 } 459 // Types. 460 for _, typ := range pkg.findTypes(symbol) { 461 if !found { 462 pkg.packageClause(true) 463 } 464 decl := typ.Decl 465 spec := pkg.findTypeSpec(decl, typ.Name) 466 trimUnexportedElems(spec) 467 // If there are multiple types defined, reduce to just this one. 468 if len(decl.Specs) > 1 { 469 decl.Specs = []ast.Spec{spec} 470 } 471 pkg.emit(typ.Doc, decl) 472 // Show associated methods, constants, etc. 473 if len(typ.Consts) > 0 || len(typ.Vars) > 0 || len(typ.Funcs) > 0 || len(typ.Methods) > 0 { 474 pkg.Printf("\n") 475 } 476 pkg.valueSummary(typ.Consts) 477 pkg.valueSummary(typ.Vars) 478 pkg.funcSummary(typ.Funcs, true) 479 pkg.funcSummary(typ.Methods, true) 480 found = true 481 } 482 if !found { 483 // See if there are methods. 484 if !pkg.printMethodDoc("", symbol) { 485 return false 486 } 487 } 488 return true 489 } 490 491 // trimUnexportedElems modifies spec in place to elide unexported fields from 492 // structs and methods from interfaces (unless the unexported flag is set). 493 func trimUnexportedElems(spec *ast.TypeSpec) { 494 if unexported { 495 return 496 } 497 switch typ := spec.Type.(type) { 498 case *ast.StructType: 499 typ.Fields = trimUnexportedFields(typ.Fields, false) 500 case *ast.InterfaceType: 501 typ.Methods = trimUnexportedFields(typ.Methods, true) 502 } 503 } 504 505 // trimUnexportedFields returns the field list trimmed of unexported fields. 506 func trimUnexportedFields(fields *ast.FieldList, isInterface bool) *ast.FieldList { 507 what := "methods" 508 if !isInterface { 509 what = "fields" 510 } 511 512 trimmed := false 513 list := make([]*ast.Field, 0, len(fields.List)) 514 for _, field := range fields.List { 515 names := field.Names 516 if len(names) == 0 { 517 // Embedded type. Use the name of the type. It must be of type ident or *ident. 518 // Nothing else is allowed. 519 switch ident := field.Type.(type) { 520 case *ast.Ident: 521 if isInterface && ident.Name == "error" && ident.Obj == nil { 522 // For documentation purposes, we consider the builtin error 523 // type special when embedded in an interface, such that it 524 // always gets shown publicly. 525 list = append(list, field) 526 continue 527 } 528 names = []*ast.Ident{ident} 529 case *ast.StarExpr: 530 // Must have the form *identifier. 531 // This is only valid on embedded types in structs. 532 if ident, ok := ident.X.(*ast.Ident); ok && !isInterface { 533 names = []*ast.Ident{ident} 534 } 535 case *ast.SelectorExpr: 536 // An embedded type may refer to a type in another package. 537 names = []*ast.Ident{ident.Sel} 538 } 539 if names == nil { 540 // Can only happen if AST is incorrect. Safe to continue with a nil list. 541 log.Print("invalid program: unexpected type for embedded field") 542 } 543 } 544 // Trims if any is unexported. Good enough in practice. 545 ok := true 546 for _, name := range names { 547 if !isExported(name.Name) { 548 trimmed = true 549 ok = false 550 break 551 } 552 } 553 if ok { 554 list = append(list, field) 555 } 556 } 557 if !trimmed { 558 return fields 559 } 560 unexportedField := &ast.Field{ 561 Type: &ast.Ident{ 562 // Hack: printer will treat this as a field with a named type. 563 // Setting Name and NamePos to ("", fields.Closing-1) ensures that 564 // when Pos and End are called on this field, they return the 565 // position right before closing '}' character. 566 Name: "", 567 NamePos: fields.Closing - 1, 568 }, 569 Comment: &ast.CommentGroup{ 570 List: []*ast.Comment{{Text: fmt.Sprintf("// Has unexported %s.\n", what)}}, 571 }, 572 } 573 return &ast.FieldList{ 574 Opening: fields.Opening, 575 List: append(list, unexportedField), 576 Closing: fields.Closing, 577 } 578 } 579 580 // printMethodDoc prints the docs for matches of symbol.method. 581 // If symbol is empty, it prints all methods that match the name. 582 // It reports whether it found any methods. 583 func (pkg *Package) printMethodDoc(symbol, method string) bool { 584 defer pkg.flush() 585 types := pkg.findTypes(symbol) 586 if types == nil { 587 if symbol == "" { 588 return false 589 } 590 pkg.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath) 591 } 592 found := false 593 for _, typ := range types { 594 for _, meth := range typ.Methods { 595 if match(method, meth.Name) { 596 decl := meth.Decl 597 decl.Body = nil 598 pkg.emit(meth.Doc, decl) 599 found = true 600 } 601 } 602 } 603 return found 604 } 605 606 // methodDoc prints the docs for matches of symbol.method. 607 func (pkg *Package) methodDoc(symbol, method string) bool { 608 defer pkg.flush() 609 return pkg.printMethodDoc(symbol, method) 610 } 611 612 // match reports whether the user's symbol matches the program's. 613 // A lower-case character in the user's string matches either case in the program's. 614 // The program string must be exported. 615 func match(user, program string) bool { 616 if !isExported(program) { 617 return false 618 } 619 if matchCase { 620 return user == program 621 } 622 for _, u := range user { 623 p, w := utf8.DecodeRuneInString(program) 624 program = program[w:] 625 if u == p { 626 continue 627 } 628 if unicode.IsLower(u) && simpleFold(u) == simpleFold(p) { 629 continue 630 } 631 return false 632 } 633 return program == "" 634 } 635 636 // simpleFold returns the minimum rune equivalent to r 637 // under Unicode-defined simple case folding. 638 func simpleFold(r rune) rune { 639 for { 640 r1 := unicode.SimpleFold(r) 641 if r1 <= r { 642 return r1 // wrapped around, found min 643 } 644 r = r1 645 } 646 }