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 5package main 6 7import ( 8 "bufio" 9 "bytes" 10 "fmt" 11 "go/ast" 12 "go/build" 13 "go/doc" 14 "go/format" 15 "go/parser" 16 "go/printer" 17 "go/token" 18 "io" 19 "io/fs" 20 "log" 21 "path/filepath" 22 "strings" 23 "unicode" 24 "unicode/utf8" 25) 26 27const ( 28 punchedCardWidth = 80 29 indent = " " 30) 31 32type Package struct { 33 writer io.Writer // Destination for output. 34 name string // Package name, json for encoding/json. 35 userPath string // String the user used to find this package. 36 pkg *ast.Package // Parsed package. 37 file *ast.File // Merged from all files in the package 38 doc *doc.Package 39 build *build.Package 40 typedValue map[*doc.Value]bool // Consts and vars related to types. 41 constructor map[*doc.Func]bool // Constructors. 42 fs *token.FileSet // Needed for printing. 43 buf pkgBuffer 44} 45 46func (pkg *Package) ToText(w io.Writer, text, prefix, codePrefix string) { 47 d := pkg.doc.Parser().Parse(text) 48 pr := pkg.doc.Printer() 49 pr.TextPrefix = prefix 50 pr.TextCodePrefix = codePrefix 51 w.Write(pr.Text(d)) 52} 53 54// pkgBuffer is a wrapper for bytes.Buffer that prints a package clause the 55// first time Write is called. 56type pkgBuffer struct { 57 pkg *Package 58 printed bool // Prevent repeated package clauses. 59 bytes.Buffer 60} 61 62func (pb *pkgBuffer) Write(p []byte) (int, error) { 63 pb.packageClause() 64 return pb.Buffer.Write(p) 65} 66 67func (pb *pkgBuffer) packageClause() { 68 if !pb.printed { 69 pb.printed = true 70 // Only show package clause for commands if requested explicitly. 71 if pb.pkg.pkg.Name != "main" || showCmd { 72 pb.pkg.packageClause() 73 } 74 } 75} 76 77type PackageError string // type returned by pkg.Fatalf. 78 79func (p PackageError) Error() string { 80 return string(p) 81} 82 83// prettyPath returns a version of the package path that is suitable for an 84// error message. It obeys the import comment if present. Also, since 85// pkg.build.ImportPath is sometimes the unhelpful "" or ".", it looks for a 86// directory name in GOROOT or GOPATH if that happens. 87func (pkg *Package) prettyPath() string { 88 path := pkg.build.ImportComment 89 if path == "" { 90 path = pkg.build.ImportPath 91 } 92 if path != "." && path != "" { 93 return path 94 } 95 // Convert the source directory into a more useful path. 96 // Also convert everything to slash-separated paths for uniform handling. 97 path = filepath.Clean(filepath.ToSlash(pkg.build.Dir)) 98 // Can we find a decent prefix? 99 if buildCtx.GOROOT != "" { 100 goroot := filepath.Join(buildCtx.GOROOT, "src") 101 if p, ok := trim(path, filepath.ToSlash(goroot)); ok { 102 return p 103 } 104 } 105 for _, gopath := range splitGopath() { 106 if p, ok := trim(path, filepath.ToSlash(gopath)); ok { 107 return p 108 } 109 } 110 return path 111} 112 113// trim trims the directory prefix from the path, paying attention 114// to the path separator. If they are the same string or the prefix 115// is not present the original is returned. The boolean reports whether 116// the prefix is present. That path and prefix have slashes for separators. 117func trim(path, prefix string) (string, bool) { 118 if !strings.HasPrefix(path, prefix) { 119 return path, false 120 } 121 if path == prefix { 122 return path, true 123 } 124 if path[len(prefix)] == '/' { 125 return path[len(prefix)+1:], true 126 } 127 return path, false // Textual prefix but not a path prefix. 128} 129 130// pkg.Fatalf is like log.Fatalf, but panics so it can be recovered in the 131// main do function, so it doesn't cause an exit. Allows testing to work 132// without running a subprocess. The log prefix will be added when 133// logged in main; it is not added here. 134func (pkg *Package) Fatalf(format string, args ...any) { 135 panic(PackageError(fmt.Sprintf(format, args...))) 136} 137 138// parsePackage turns the build package we found into a parsed package 139// we can then use to generate documentation. 140func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Package { 141 // include tells parser.ParseDir which files to include. 142 // That means the file must be in the build package's GoFiles or CgoFiles 143 // list only (no tag-ignored files, tests, swig or other non-Go files). 144 include := func(info fs.FileInfo) bool { 145 for _, name := range pkg.GoFiles { 146 if name == info.Name() { 147 return true 148 } 149 } 150 for _, name := range pkg.CgoFiles { 151 if name == info.Name() { 152 return true 153 } 154 } 155 return false 156 } 157 fset := token.NewFileSet() 158 pkgs, err := parser.ParseDir(fset, pkg.Dir, include, parser.ParseComments) 159 if err != nil { 160 log.Fatal(err) 161 } 162 // Make sure they are all in one package. 163 if len(pkgs) == 0 { 164 log.Fatalf("no source-code package in directory %s", pkg.Dir) 165 } 166 if len(pkgs) > 1 { 167 log.Fatalf("multiple packages in directory %s", pkg.Dir) 168 } 169 astPkg := pkgs[pkg.Name] 170 171 // TODO: go/doc does not include typed constants in the constants 172 // list, which is what we want. For instance, time.Sunday is of type 173 // time.Weekday, so it is defined in the type but not in the 174 // Consts list for the package. This prevents 175 // go doc time.Sunday 176 // from finding the symbol. Work around this for now, but we 177 // should fix it in go/doc. 178 // A similar story applies to factory functions. 179 mode := doc.AllDecls 180 if showSrc { 181 mode |= doc.PreserveAST // See comment for Package.emit. 182 } 183 docPkg := doc.New(astPkg, pkg.ImportPath, mode) 184 typedValue := make(map[*doc.Value]bool) 185 constructor := make(map[*doc.Func]bool) 186 for _, typ := range docPkg.Types { 187 docPkg.Consts = append(docPkg.Consts, typ.Consts...) 188 docPkg.Vars = append(docPkg.Vars, typ.Vars...) 189 docPkg.Funcs = append(docPkg.Funcs, typ.Funcs...) 190 if isExported(typ.Name) { 191 for _, value := range typ.Consts { 192 typedValue[value] = true 193 } 194 for _, value := range typ.Vars { 195 typedValue[value] = true 196 } 197 for _, fun := range typ.Funcs { 198 // We don't count it as a constructor bound to the type 199 // if the type itself is not exported. 200 constructor[fun] = true 201 } 202 } 203 } 204 205 p := &Package{ 206 writer: writer, 207 name: pkg.Name, 208 userPath: userPath, 209 pkg: astPkg, 210 file: ast.MergePackageFiles(astPkg, 0), 211 doc: docPkg, 212 typedValue: typedValue, 213 constructor: constructor, 214 build: pkg, 215 fs: fset, 216 } 217 p.buf.pkg = p 218 return p 219} 220 221func (pkg *Package) Printf(format string, args ...any) { 222 fmt.Fprintf(&pkg.buf, format, args...) 223} 224 225func (pkg *Package) flush() { 226 _, err := pkg.writer.Write(pkg.buf.Bytes()) 227 if err != nil { 228 log.Fatal(err) 229 } 230 pkg.buf.Reset() // Not needed, but it's a flush. 231} 232 233var newlineBytes = []byte("\n\n") // We never ask for more than 2. 234 235// newlines guarantees there are n newlines at the end of the buffer. 236func (pkg *Package) newlines(n int) { 237 for !bytes.HasSuffix(pkg.buf.Bytes(), newlineBytes[:n]) { 238 pkg.buf.WriteRune('\n') 239 } 240} 241 242// emit prints the node. If showSrc is true, it ignores the provided comment, 243// assuming the comment is in the node itself. Otherwise, the go/doc package 244// clears the stuff we don't want to print anyway. It's a bit of a magic trick. 245func (pkg *Package) emit(comment string, node ast.Node) { 246 if node != nil { 247 var arg any = node 248 if showSrc { 249 // Need an extra little dance to get internal comments to appear. 250 arg = &printer.CommentedNode{ 251 Node: node, 252 Comments: pkg.file.Comments, 253 } 254 } 255 err := format.Node(&pkg.buf, pkg.fs, arg) 256 if err != nil { 257 log.Fatal(err) 258 } 259 if comment != "" && !showSrc { 260 pkg.newlines(1) 261 pkg.ToText(&pkg.buf, comment, indent, indent+indent) 262 pkg.newlines(2) // Blank line after comment to separate from next item. 263 } else { 264 pkg.newlines(1) 265 } 266 } 267} 268 269// oneLineNode returns a one-line summary of the given input node. 270func (pkg *Package) oneLineNode(node ast.Node) string { 271 const maxDepth = 10 272 return pkg.oneLineNodeDepth(node, maxDepth) 273} 274 275// oneLineNodeDepth returns a one-line summary of the given input node. 276// The depth specifies the maximum depth when traversing the AST. 277func (pkg *Package) oneLineNodeDepth(node ast.Node, depth int) string { 278 const dotDotDot = "..." 279 if depth == 0 { 280 return dotDotDot 281 } 282 depth-- 283 284 switch n := node.(type) { 285 case nil: 286 return "" 287 288 case *ast.GenDecl: 289 // Formats const and var declarations. 290 trailer := "" 291 if len(n.Specs) > 1 { 292 trailer = " " + dotDotDot 293 } 294 295 // Find the first relevant spec. 296 typ := "" 297 for i, spec := range n.Specs { 298 valueSpec := spec.(*ast.ValueSpec) // Must succeed; we can't mix types in one GenDecl. 299 300 // The type name may carry over from a previous specification in the 301 // case of constants and iota. 302 if valueSpec.Type != nil { 303 typ = fmt.Sprintf(" %s", pkg.oneLineNodeDepth(valueSpec.Type, depth)) 304 } else if len(valueSpec.Values) > 0 { 305 typ = "" 306 } 307 308 if !isExported(valueSpec.Names[0].Name) { 309 continue 310 } 311 val := "" 312 if i < len(valueSpec.Values) && valueSpec.Values[i] != nil { 313 val = fmt.Sprintf(" = %s", pkg.oneLineNodeDepth(valueSpec.Values[i], depth)) 314 } 315 return fmt.Sprintf("%s %s%s%s%s", n.Tok, valueSpec.Names[0], typ, val, trailer) 316 } 317 return "" 318 319 case *ast.FuncDecl: 320 // Formats func declarations. 321 name := n.Name.Name 322 recv := pkg.oneLineNodeDepth(n.Recv, depth) 323 if len(recv) > 0 { 324 recv = "(" + recv + ") " 325 } 326 fnc := pkg.oneLineNodeDepth(n.Type, depth) 327 fnc = strings.TrimPrefix(fnc, "func") 328 return fmt.Sprintf("func %s%s%s", recv, name, fnc) 329 330 case *ast.TypeSpec: 331 sep := " " 332 if n.Assign.IsValid() { 333 sep = " = " 334 } 335 tparams := pkg.formatTypeParams(n.TypeParams, depth) 336 return fmt.Sprintf("type %s%s%s%s", n.Name.Name, tparams, sep, pkg.oneLineNodeDepth(n.Type, depth)) 337 338 case *ast.FuncType: 339 var params []string 340 if n.Params != nil { 341 for _, field := range n.Params.List { 342 params = append(params, pkg.oneLineField(field, depth)) 343 } 344 } 345 needParens := false 346 var results []string 347 if n.Results != nil { 348 needParens = needParens || len(n.Results.List) > 1 349 for _, field := range n.Results.List { 350 needParens = needParens || len(field.Names) > 0 351 results = append(results, pkg.oneLineField(field, depth)) 352 } 353 } 354 355 tparam := pkg.formatTypeParams(n.TypeParams, depth) 356 param := joinStrings(params) 357 if len(results) == 0 { 358 return fmt.Sprintf("func%s(%s)", tparam, param) 359 } 360 result := joinStrings(results) 361 if !needParens { 362 return fmt.Sprintf("func%s(%s) %s", tparam, param, result) 363 } 364 return fmt.Sprintf("func%s(%s) (%s)", tparam, param, result) 365 366 case *ast.StructType: 367 if n.Fields == nil || len(n.Fields.List) == 0 { 368 return "struct{}" 369 } 370 return "struct{ ... }" 371 372 case *ast.InterfaceType: 373 if n.Methods == nil || len(n.Methods.List) == 0 { 374 return "interface{}" 375 } 376 return "interface{ ... }" 377 378 case *ast.FieldList: 379 if n == nil || len(n.List) == 0 { 380 return "" 381 } 382 if len(n.List) == 1 { 383 return pkg.oneLineField(n.List[0], depth) 384 } 385 return dotDotDot 386 387 case *ast.FuncLit: 388 return pkg.oneLineNodeDepth(n.Type, depth) + " { ... }" 389 390 case *ast.CompositeLit: 391 typ := pkg.oneLineNodeDepth(n.Type, depth) 392 if len(n.Elts) == 0 { 393 return fmt.Sprintf("%s{}", typ) 394 } 395 return fmt.Sprintf("%s{ %s }", typ, dotDotDot) 396 397 case *ast.ArrayType: 398 length := pkg.oneLineNodeDepth(n.Len, depth) 399 element := pkg.oneLineNodeDepth(n.Elt, depth) 400 return fmt.Sprintf("[%s]%s", length, element) 401 402 case *ast.MapType: 403 key := pkg.oneLineNodeDepth(n.Key, depth) 404 value := pkg.oneLineNodeDepth(n.Value, depth) 405 return fmt.Sprintf("map[%s]%s", key, value) 406 407 case *ast.CallExpr: 408 fnc := pkg.oneLineNodeDepth(n.Fun, depth) 409 var args []string 410 for _, arg := range n.Args { 411 args = append(args, pkg.oneLineNodeDepth(arg, depth)) 412 } 413 return fmt.Sprintf("%s(%s)", fnc, joinStrings(args)) 414 415 case *ast.UnaryExpr: 416 return fmt.Sprintf("%s%s", n.Op, pkg.oneLineNodeDepth(n.X, depth)) 417 418 case *ast.Ident: 419 return n.Name 420 421 default: 422 // As a fallback, use default formatter for all unknown node types. 423 buf := new(strings.Builder) 424 format.Node(buf, pkg.fs, node) 425 s := buf.String() 426 if strings.Contains(s, "\n") { 427 return dotDotDot 428 } 429 return s 430 } 431} 432 433func (pkg *Package) formatTypeParams(list *ast.FieldList, depth int) string { 434 if list.NumFields() == 0 { 435 return "" 436 } 437 var tparams []string 438 for _, field := range list.List { 439 tparams = append(tparams, pkg.oneLineField(field, depth)) 440 } 441 return "[" + joinStrings(tparams) + "]" 442} 443 444// oneLineField returns a one-line summary of the field. 445func (pkg *Package) oneLineField(field *ast.Field, depth int) string { 446 var names []string 447 for _, name := range field.Names { 448 names = append(names, name.Name) 449 } 450 if len(names) == 0 { 451 return pkg.oneLineNodeDepth(field.Type, depth) 452 } 453 return joinStrings(names) + " " + pkg.oneLineNodeDepth(field.Type, depth) 454} 455 456// joinStrings formats the input as a comma-separated list, 457// but truncates the list at some reasonable length if necessary. 458func joinStrings(ss []string) string { 459 var n int 460 for i, s := range ss { 461 n += len(s) + len(", ") 462 if n > punchedCardWidth { 463 ss = append(ss[:i:i], "...") 464 break 465 } 466 } 467 return strings.Join(ss, ", ") 468} 469 470// printHeader prints a header for the section named s, adding a blank line on each side. 471func (pkg *Package) printHeader(s string) { 472 pkg.Printf("\n%s\n\n", s) 473} 474 475// constsDoc prints all const documentation, if any, including a header. 476// The one argument is the valueDoc registry. 477func (pkg *Package) constsDoc(printed map[*ast.GenDecl]bool) { 478 var header bool 479 for _, value := range pkg.doc.Consts { 480 // Constants and variables come in groups, and valueDoc prints 481 // all the items in the group. We only need to find one exported symbol. 482 for _, name := range value.Names { 483 if isExported(name) && !pkg.typedValue[value] { 484 if !header { 485 pkg.printHeader("CONSTANTS") 486 header = true 487 } 488 pkg.valueDoc(value, printed) 489 break 490 } 491 } 492 } 493} 494 495// varsDoc prints all var documentation, if any, including a header. 496// Printed is the valueDoc registry. 497func (pkg *Package) varsDoc(printed map[*ast.GenDecl]bool) { 498 var header bool 499 for _, value := range pkg.doc.Vars { 500 // Constants and variables come in groups, and valueDoc prints 501 // all the items in the group. We only need to find one exported symbol. 502 for _, name := range value.Names { 503 if isExported(name) && !pkg.typedValue[value] { 504 if !header { 505 pkg.printHeader("VARIABLES") 506 header = true 507 } 508 pkg.valueDoc(value, printed) 509 break 510 } 511 } 512 } 513} 514 515// funcsDoc prints all func documentation, if any, including a header. 516func (pkg *Package) funcsDoc() { 517 var header bool 518 for _, fun := range pkg.doc.Funcs { 519 if isExported(fun.Name) && !pkg.constructor[fun] { 520 if !header { 521 pkg.printHeader("FUNCTIONS") 522 header = true 523 } 524 pkg.emit(fun.Doc, fun.Decl) 525 } 526 } 527} 528 529// funcsDoc prints all type documentation, if any, including a header. 530func (pkg *Package) typesDoc() { 531 var header bool 532 for _, typ := range pkg.doc.Types { 533 if isExported(typ.Name) { 534 if !header { 535 pkg.printHeader("TYPES") 536 header = true 537 } 538 pkg.typeDoc(typ) 539 } 540 } 541} 542 543// packageDoc prints the docs for the package. 544func (pkg *Package) packageDoc() { 545 pkg.Printf("") // Trigger the package clause; we know the package exists. 546 if showAll || !short { 547 pkg.ToText(&pkg.buf, pkg.doc.Doc, "", indent) 548 pkg.newlines(1) 549 } 550 551 switch { 552 case showAll: 553 printed := make(map[*ast.GenDecl]bool) // valueDoc registry 554 pkg.constsDoc(printed) 555 pkg.varsDoc(printed) 556 pkg.funcsDoc() 557 pkg.typesDoc() 558 559 case pkg.pkg.Name == "main" && !showCmd: 560 // Show only package docs for commands. 561 return 562 563 default: 564 if !short { 565 pkg.newlines(2) // Guarantee blank line before the components. 566 } 567 pkg.valueSummary(pkg.doc.Consts, false) 568 pkg.valueSummary(pkg.doc.Vars, false) 569 pkg.funcSummary(pkg.doc.Funcs, false) 570 pkg.typeSummary() 571 } 572 573 if !short { 574 pkg.bugs() 575 } 576} 577 578// packageClause prints the package clause. 579func (pkg *Package) packageClause() { 580 if short { 581 return 582 } 583 importPath := pkg.build.ImportComment 584 if importPath == "" { 585 importPath = pkg.build.ImportPath 586 } 587 588 // If we're using modules, the import path derived from module code locations wins. 589 // If we did a file system scan, we knew the import path when we found the directory. 590 // But if we started with a directory name, we never knew the import path. 591 // Either way, we don't know it now, and it's cheap to (re)compute it. 592 if usingModules { 593 for _, root := range codeRoots() { 594 if pkg.build.Dir == root.dir { 595 importPath = root.importPath 596 break 597 } 598 if strings.HasPrefix(pkg.build.Dir, root.dir+string(filepath.Separator)) { 599 suffix := filepath.ToSlash(pkg.build.Dir[len(root.dir)+1:]) 600 if root.importPath == "" { 601 importPath = suffix 602 } else { 603 importPath = root.importPath + "/" + suffix 604 } 605 break 606 } 607 } 608 } 609 610 pkg.Printf("package %s // import %q\n\n", pkg.name, importPath) 611 if !usingModules && importPath != pkg.build.ImportPath { 612 pkg.Printf("WARNING: package source is installed in %q\n", pkg.build.ImportPath) 613 } 614} 615 616// valueSummary prints a one-line summary for each set of values and constants. 617// If all the types in a constant or variable declaration belong to the same 618// type they can be printed by typeSummary, and so can be suppressed here. 619func (pkg *Package) valueSummary(values []*doc.Value, showGrouped bool) { 620 var isGrouped map[*doc.Value]bool 621 if !showGrouped { 622 isGrouped = make(map[*doc.Value]bool) 623 for _, typ := range pkg.doc.Types { 624 if !isExported(typ.Name) { 625 continue 626 } 627 for _, c := range typ.Consts { 628 isGrouped[c] = true 629 } 630 for _, v := range typ.Vars { 631 isGrouped[v] = true 632 } 633 } 634 } 635 636 for _, value := range values { 637 if !isGrouped[value] { 638 if decl := pkg.oneLineNode(value.Decl); decl != "" { 639 pkg.Printf("%s\n", decl) 640 } 641 } 642 } 643} 644 645// funcSummary prints a one-line summary for each function. Constructors 646// are printed by typeSummary, below, and so can be suppressed here. 647func (pkg *Package) funcSummary(funcs []*doc.Func, showConstructors bool) { 648 for _, fun := range funcs { 649 // Exported functions only. The go/doc package does not include methods here. 650 if isExported(fun.Name) { 651 if showConstructors || !pkg.constructor[fun] { 652 pkg.Printf("%s\n", pkg.oneLineNode(fun.Decl)) 653 } 654 } 655 } 656} 657 658// typeSummary prints a one-line summary for each type, followed by its constructors. 659func (pkg *Package) typeSummary() { 660 for _, typ := range pkg.doc.Types { 661 for _, spec := range typ.Decl.Specs { 662 typeSpec := spec.(*ast.TypeSpec) // Must succeed. 663 if isExported(typeSpec.Name.Name) { 664 pkg.Printf("%s\n", pkg.oneLineNode(typeSpec)) 665 // Now print the consts, vars, and constructors. 666 for _, c := range typ.Consts { 667 if decl := pkg.oneLineNode(c.Decl); decl != "" { 668 pkg.Printf(indent+"%s\n", decl) 669 } 670 } 671 for _, v := range typ.Vars { 672 if decl := pkg.oneLineNode(v.Decl); decl != "" { 673 pkg.Printf(indent+"%s\n", decl) 674 } 675 } 676 for _, constructor := range typ.Funcs { 677 if isExported(constructor.Name) { 678 pkg.Printf(indent+"%s\n", pkg.oneLineNode(constructor.Decl)) 679 } 680 } 681 } 682 } 683 } 684} 685 686// bugs prints the BUGS information for the package. 687// TODO: Provide access to TODOs and NOTEs as well (very noisy so off by default)? 688func (pkg *Package) bugs() { 689 if pkg.doc.Notes["BUG"] == nil { 690 return 691 } 692 pkg.Printf("\n") 693 for _, note := range pkg.doc.Notes["BUG"] { 694 pkg.Printf("%s: %v\n", "BUG", note.Body) 695 } 696} 697 698// findValues finds the doc.Values that describe the symbol. 699func (pkg *Package) findValues(symbol string, docValues []*doc.Value) (values []*doc.Value) { 700 for _, value := range docValues { 701 for _, name := range value.Names { 702 if match(symbol, name) { 703 values = append(values, value) 704 } 705 } 706 } 707 return 708} 709 710// findFuncs finds the doc.Funcs that describes the symbol. 711func (pkg *Package) findFuncs(symbol string) (funcs []*doc.Func) { 712 for _, fun := range pkg.doc.Funcs { 713 if match(symbol, fun.Name) { 714 funcs = append(funcs, fun) 715 } 716 } 717 return 718} 719 720// findTypes finds the doc.Types that describes the symbol. 721// If symbol is empty, it finds all exported types. 722func (pkg *Package) findTypes(symbol string) (types []*doc.Type) { 723 for _, typ := range pkg.doc.Types { 724 if symbol == "" && isExported(typ.Name) || match(symbol, typ.Name) { 725 types = append(types, typ) 726 } 727 } 728 return 729} 730 731// findTypeSpec returns the ast.TypeSpec within the declaration that defines the symbol. 732// The name must match exactly. 733func (pkg *Package) findTypeSpec(decl *ast.GenDecl, symbol string) *ast.TypeSpec { 734 for _, spec := range decl.Specs { 735 typeSpec := spec.(*ast.TypeSpec) // Must succeed. 736 if symbol == typeSpec.Name.Name { 737 return typeSpec 738 } 739 } 740 return nil 741} 742 743// symbolDoc prints the docs for symbol. There may be multiple matches. 744// If symbol matches a type, output includes its methods factories and associated constants. 745// If there is no top-level symbol, symbolDoc looks for methods that match. 746func (pkg *Package) symbolDoc(symbol string) bool { 747 found := false 748 // Functions. 749 for _, fun := range pkg.findFuncs(symbol) { 750 // Symbol is a function. 751 decl := fun.Decl 752 pkg.emit(fun.Doc, decl) 753 found = true 754 } 755 // Constants and variables behave the same. 756 values := pkg.findValues(symbol, pkg.doc.Consts) 757 values = append(values, pkg.findValues(symbol, pkg.doc.Vars)...) 758 printed := make(map[*ast.GenDecl]bool) // valueDoc registry 759 for _, value := range values { 760 pkg.valueDoc(value, printed) 761 found = true 762 } 763 // Types. 764 for _, typ := range pkg.findTypes(symbol) { 765 pkg.typeDoc(typ) 766 found = true 767 } 768 if !found { 769 // See if there are methods. 770 if !pkg.printMethodDoc("", symbol) { 771 return false 772 } 773 } 774 return true 775} 776 777// valueDoc prints the docs for a constant or variable. The printed map records 778// which values have been printed already to avoid duplication. Otherwise, a 779// declaration like: 780// 781// const ( c = 1; C = 2 ) 782// 783// … could be printed twice if the -u flag is set, as it matches twice. 784func (pkg *Package) valueDoc(value *doc.Value, printed map[*ast.GenDecl]bool) { 785 if printed[value.Decl] { 786 return 787 } 788 // Print each spec only if there is at least one exported symbol in it. 789 // (See issue 11008.) 790 // TODO: Should we elide unexported symbols from a single spec? 791 // It's an unlikely scenario, probably not worth the trouble. 792 // TODO: Would be nice if go/doc did this for us. 793 specs := make([]ast.Spec, 0, len(value.Decl.Specs)) 794 var typ ast.Expr 795 for _, spec := range value.Decl.Specs { 796 vspec := spec.(*ast.ValueSpec) 797 798 // The type name may carry over from a previous specification in the 799 // case of constants and iota. 800 if vspec.Type != nil { 801 typ = vspec.Type 802 } 803 804 for _, ident := range vspec.Names { 805 if showSrc || isExported(ident.Name) { 806 if vspec.Type == nil && vspec.Values == nil && typ != nil { 807 // This a standalone identifier, as in the case of iota usage. 808 // Thus, assume the type comes from the previous type. 809 vspec.Type = &ast.Ident{ 810 Name: pkg.oneLineNode(typ), 811 NamePos: vspec.End() - 1, 812 } 813 } 814 815 specs = append(specs, vspec) 816 typ = nil // Only inject type on first exported identifier 817 break 818 } 819 } 820 } 821 if len(specs) == 0 { 822 return 823 } 824 value.Decl.Specs = specs 825 pkg.emit(value.Doc, value.Decl) 826 printed[value.Decl] = true 827} 828 829// typeDoc prints the docs for a type, including constructors and other items 830// related to it. 831func (pkg *Package) typeDoc(typ *doc.Type) { 832 decl := typ.Decl 833 spec := pkg.findTypeSpec(decl, typ.Name) 834 trimUnexportedElems(spec) 835 // If there are multiple types defined, reduce to just this one. 836 if len(decl.Specs) > 1 { 837 decl.Specs = []ast.Spec{spec} 838 } 839 pkg.emit(typ.Doc, decl) 840 pkg.newlines(2) 841 // Show associated methods, constants, etc. 842 if showAll { 843 printed := make(map[*ast.GenDecl]bool) // valueDoc registry 844 // We can use append here to print consts, then vars. Ditto for funcs and methods. 845 values := typ.Consts 846 values = append(values, typ.Vars...) 847 for _, value := range values { 848 for _, name := range value.Names { 849 if isExported(name) { 850 pkg.valueDoc(value, printed) 851 break 852 } 853 } 854 } 855 funcs := typ.Funcs 856 funcs = append(funcs, typ.Methods...) 857 for _, fun := range funcs { 858 if isExported(fun.Name) { 859 pkg.emit(fun.Doc, fun.Decl) 860 if fun.Doc == "" { 861 pkg.newlines(2) 862 } 863 } 864 } 865 } else { 866 pkg.valueSummary(typ.Consts, true) 867 pkg.valueSummary(typ.Vars, true) 868 pkg.funcSummary(typ.Funcs, true) 869 pkg.funcSummary(typ.Methods, true) 870 } 871} 872 873// trimUnexportedElems modifies spec in place to elide unexported fields from 874// structs and methods from interfaces (unless the unexported flag is set or we 875// are asked to show the original source). 876func trimUnexportedElems(spec *ast.TypeSpec) { 877 if showSrc { 878 return 879 } 880 switch typ := spec.Type.(type) { 881 case *ast.StructType: 882 typ.Fields = trimUnexportedFields(typ.Fields, false) 883 case *ast.InterfaceType: 884 typ.Methods = trimUnexportedFields(typ.Methods, true) 885 } 886} 887 888// trimUnexportedFields returns the field list trimmed of unexported fields. 889func trimUnexportedFields(fields *ast.FieldList, isInterface bool) *ast.FieldList { 890 what := "methods" 891 if !isInterface { 892 what = "fields" 893 } 894 895 trimmed := false 896 list := make([]*ast.Field, 0, len(fields.List)) 897 for _, field := range fields.List { 898 // When printing fields we normally print field.Doc. 899 // Here we are going to pass the AST to go/format, 900 // which will print the comments from the AST, 901 // not field.Doc which is from go/doc. 902 // The two are similar but not identical; 903 // for example, field.Doc does not include directives. 904 // In order to consistently print field.Doc, 905 // we replace the comment in the AST with field.Doc. 906 // That will cause go/format to print what we want. 907 // See issue #56592. 908 if field.Doc != nil { 909 doc := field.Doc 910 text := doc.Text() 911 912 trailingBlankLine := len(doc.List[len(doc.List)-1].Text) == 2 913 if !trailingBlankLine { 914 // Remove trailing newline. 915 lt := len(text) 916 if lt > 0 && text[lt-1] == '\n' { 917 text = text[:lt-1] 918 } 919 } 920 921 start := doc.List[0].Slash 922 doc.List = doc.List[:0] 923 for _, line := range strings.Split(text, "\n") { 924 prefix := "// " 925 if len(line) > 0 && line[0] == '\t' { 926 prefix = "//" 927 } 928 doc.List = append(doc.List, &ast.Comment{ 929 Text: prefix + line, 930 }) 931 } 932 doc.List[0].Slash = start 933 } 934 935 names := field.Names 936 if len(names) == 0 { 937 // Embedded type. Use the name of the type. It must be of the form ident or 938 // pkg.ident (for structs and interfaces), or *ident or *pkg.ident (structs only). 939 // Or a type embedded in a constraint. 940 // Nothing else is allowed. 941 ty := field.Type 942 if se, ok := field.Type.(*ast.StarExpr); !isInterface && ok { 943 // The form *ident or *pkg.ident is only valid on 944 // embedded types in structs. 945 ty = se.X 946 } 947 constraint := false 948 switch ident := ty.(type) { 949 case *ast.Ident: 950 if isInterface && ident.Name == "error" && ident.Obj == nil { 951 // For documentation purposes, we consider the builtin error 952 // type special when embedded in an interface, such that it 953 // always gets shown publicly. 954 list = append(list, field) 955 continue 956 } 957 names = []*ast.Ident{ident} 958 case *ast.SelectorExpr: 959 // An embedded type may refer to a type in another package. 960 names = []*ast.Ident{ident.Sel} 961 default: 962 // An approximation or union or type 963 // literal in an interface. 964 constraint = true 965 } 966 if names == nil && !constraint { 967 // Can only happen if AST is incorrect. Safe to continue with a nil list. 968 log.Print("invalid program: unexpected type for embedded field") 969 } 970 } 971 // Trims if any is unexported. Good enough in practice. 972 ok := true 973 if !unexported { 974 for _, name := range names { 975 if !isExported(name.Name) { 976 trimmed = true 977 ok = false 978 break 979 } 980 } 981 } 982 if ok { 983 list = append(list, field) 984 } 985 } 986 if !trimmed { 987 return fields 988 } 989 unexportedField := &ast.Field{ 990 Type: &ast.Ident{ 991 // Hack: printer will treat this as a field with a named type. 992 // Setting Name and NamePos to ("", fields.Closing-1) ensures that 993 // when Pos and End are called on this field, they return the 994 // position right before closing '}' character. 995 Name: "", 996 NamePos: fields.Closing - 1, 997 }, 998 Comment: &ast.CommentGroup{ 999 List: []*ast.Comment{{Text: fmt.Sprintf("// Has unexported %s.\n", what)}}, 1000 }, 1001 } 1002 return &ast.FieldList{ 1003 Opening: fields.Opening, 1004 List: append(list, unexportedField), 1005 Closing: fields.Closing, 1006 } 1007} 1008 1009// printMethodDoc prints the docs for matches of symbol.method. 1010// If symbol is empty, it prints all methods for any concrete type 1011// that match the name. It reports whether it found any methods. 1012func (pkg *Package) printMethodDoc(symbol, method string) bool { 1013 types := pkg.findTypes(symbol) 1014 if types == nil { 1015 if symbol == "" { 1016 return false 1017 } 1018 pkg.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath) 1019 } 1020 found := false 1021 for _, typ := range types { 1022 if len(typ.Methods) > 0 { 1023 for _, meth := range typ.Methods { 1024 if match(method, meth.Name) { 1025 decl := meth.Decl 1026 pkg.emit(meth.Doc, decl) 1027 found = true 1028 } 1029 } 1030 continue 1031 } 1032 if symbol == "" { 1033 continue 1034 } 1035 // Type may be an interface. The go/doc package does not attach 1036 // an interface's methods to the doc.Type. We need to dig around. 1037 spec := pkg.findTypeSpec(typ.Decl, typ.Name) 1038 inter, ok := spec.Type.(*ast.InterfaceType) 1039 if !ok { 1040 // Not an interface type. 1041 continue 1042 } 1043 1044 // Collect and print only the methods that match. 1045 var methods []*ast.Field 1046 for _, iMethod := range inter.Methods.List { 1047 // This is an interface, so there can be only one name. 1048 // TODO: Anonymous methods (embedding) 1049 if len(iMethod.Names) == 0 { 1050 continue 1051 } 1052 name := iMethod.Names[0].Name 1053 if match(method, name) { 1054 methods = append(methods, iMethod) 1055 found = true 1056 } 1057 } 1058 if found { 1059 pkg.Printf("type %s ", spec.Name) 1060 inter.Methods.List, methods = methods, inter.Methods.List 1061 err := format.Node(&pkg.buf, pkg.fs, inter) 1062 if err != nil { 1063 log.Fatal(err) 1064 } 1065 pkg.newlines(1) 1066 // Restore the original methods. 1067 inter.Methods.List = methods 1068 } 1069 } 1070 return found 1071} 1072 1073// printFieldDoc prints the docs for matches of symbol.fieldName. 1074// It reports whether it found any field. 1075// Both symbol and fieldName must be non-empty or it returns false. 1076func (pkg *Package) printFieldDoc(symbol, fieldName string) bool { 1077 if symbol == "" || fieldName == "" { 1078 return false 1079 } 1080 types := pkg.findTypes(symbol) 1081 if types == nil { 1082 pkg.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath) 1083 } 1084 found := false 1085 numUnmatched := 0 1086 for _, typ := range types { 1087 // Type must be a struct. 1088 spec := pkg.findTypeSpec(typ.Decl, typ.Name) 1089 structType, ok := spec.Type.(*ast.StructType) 1090 if !ok { 1091 // Not a struct type. 1092 continue 1093 } 1094 for _, field := range structType.Fields.List { 1095 // TODO: Anonymous fields. 1096 for _, name := range field.Names { 1097 if !match(fieldName, name.Name) { 1098 numUnmatched++ 1099 continue 1100 } 1101 if !found { 1102 pkg.Printf("type %s struct {\n", typ.Name) 1103 } 1104 if field.Doc != nil { 1105 // To present indented blocks in comments correctly, process the comment as 1106 // a unit before adding the leading // to each line. 1107 docBuf := new(bytes.Buffer) 1108 pkg.ToText(docBuf, field.Doc.Text(), "", indent) 1109 scanner := bufio.NewScanner(docBuf) 1110 for scanner.Scan() { 1111 fmt.Fprintf(&pkg.buf, "%s// %s\n", indent, scanner.Bytes()) 1112 } 1113 } 1114 s := pkg.oneLineNode(field.Type) 1115 lineComment := "" 1116 if field.Comment != nil { 1117 lineComment = fmt.Sprintf(" %s", field.Comment.List[0].Text) 1118 } 1119 pkg.Printf("%s%s %s%s\n", indent, name, s, lineComment) 1120 found = true 1121 } 1122 } 1123 } 1124 if found { 1125 if numUnmatched > 0 { 1126 pkg.Printf("\n // ... other fields elided ...\n") 1127 } 1128 pkg.Printf("}\n") 1129 } 1130 return found 1131} 1132 1133// match reports whether the user's symbol matches the program's. 1134// A lower-case character in the user's string matches either case in the program's. 1135// The program string must be exported. 1136func match(user, program string) bool { 1137 if !isExported(program) { 1138 return false 1139 } 1140 if matchCase { 1141 return user == program 1142 } 1143 for _, u := range user { 1144 p, w := utf8.DecodeRuneInString(program) 1145 program = program[w:] 1146 if u == p { 1147 continue 1148 } 1149 if unicode.IsLower(u) && simpleFold(u) == simpleFold(p) { 1150 continue 1151 } 1152 return false 1153 } 1154 return program == "" 1155} 1156 1157// simpleFold returns the minimum rune equivalent to r 1158// under Unicode-defined simple case folding. 1159func simpleFold(r rune) rune { 1160 for { 1161 r1 := unicode.SimpleFold(r) 1162 if r1 <= r { 1163 return r1 // wrapped around, found min 1164 } 1165 r = r1 1166 } 1167} 1168