1// Copyright 2015 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package doc67import (8 "bytes"9 "fmt"10 "go/ast"11 "go/doc"12 "go/format"13 "go/parser"14 "go/printer"15 "go/token"16 "io"17 "io/fs"18 "log"19 "path/filepath"20 "strings"21 "unicode"22 "unicode/utf8"2324 "cmd/go/internal/cfg"25 "cmd/go/internal/load"26)2728const (29 punchedCardWidth = 8030 indent = " "31)3233type Package struct {34 writer io.Writer // Destination for output.35 name string // Package name, json for encoding/json.36 userPath string // String the user used to find this package.37 pkg *ast.Package // Parsed package.38 file *ast.File // Merged from all files in the package39 doc *doc.Package40 build *load.Package41 typedValue map[*doc.Value]bool // Consts and vars related to types.42 constructor map[*doc.Func]bool // Constructors.43 fs *token.FileSet // Needed for printing.44 buf pkgBuffer45}4647func (pkg *Package) ToText(w io.Writer, text, prefix, codePrefix string) {48 d := pkg.doc.Parser().Parse(text)49 pr := pkg.doc.Printer()50 pr.TextPrefix = prefix51 pr.TextCodePrefix = codePrefix52 w.Write(pr.Text(d))53}5455// pkgBuffer is a wrapper for bytes.Buffer that prints a package clause the56// first time Write is called.57type pkgBuffer struct {58 pkg *Package59 printed bool // Prevent repeated package clauses.60 bytes.Buffer61}6263func (pb *pkgBuffer) Write(p []byte) (int, error) {64 pb.packageClause()65 return pb.Buffer.Write(p)66}6768func (pb *pkgBuffer) packageClause() {69 if !pb.printed {70 pb.printed = true71 // Only show package clause for commands if requested explicitly.72 if pb.pkg.pkg.Name != "main" || showCmd {73 pb.pkg.packageClause()74 }75 }76}7778type PackageError string // type returned by pkg.Fatalf.7980func (p PackageError) Error() string {81 return string(p)82}8384// prettyPath returns a version of the package path that is suitable for an85// error message. It obeys the import comment if present. Also, since86// pkg.build.ImportPath is sometimes the unhelpful "" or ".", it looks for a87// directory name in GOROOT or GOPATH if that happens.88func (pkg *Package) prettyPath() string {89 path := pkg.build.ImportComment90 if path == "" {91 path = pkg.build.ImportPath92 }93 if path != "." && path != "" {94 return path95 }96 // Convert the source directory into a more useful path.97 // Also convert everything to slash-separated paths for uniform handling.98 path = filepath.Clean(filepath.ToSlash(pkg.build.Dir))99 // Can we find a decent prefix?100 if cfg.GOROOT != "" {101 goroot := filepath.Join(cfg.GOROOT, "src")102 if p, ok := trim(path, filepath.ToSlash(goroot)); ok {103 return p104 }105 }106 for _, gopath := range splitGopath() {107 if p, ok := trim(path, filepath.ToSlash(gopath)); ok {108 return p109 }110 }111 return path112}113114// trim trims the directory prefix from the path, paying attention115// to the path separator. If they are the same string or the prefix116// is not present the original is returned. The boolean reports whether117// the prefix is present. That path and prefix have slashes for separators.118func trim(path, prefix string) (string, bool) {119 if !strings.HasPrefix(path, prefix) {120 return path, false121 }122 if path == prefix {123 return path, true124 }125 if path[len(prefix)] == '/' {126 return path[len(prefix)+1:], true127 }128 return path, false // Textual prefix but not a path prefix.129}130131// pkg.Fatalf is like log.Fatalf, but panics so it can be recovered in the132// main do function, so it doesn't cause an exit. Allows testing to work133// without running a subprocess. The log prefix will be added when134// logged in main; it is not added here.135func (pkg *Package) Fatalf(format string, args ...any) {136 panic(PackageError(fmt.Sprintf(format, args...)))137}138139// parsePackage turns the build package we found into a parsed package140// we can then use to generate documentation.141func parsePackage(writer io.Writer, pkg *load.Package, userPath string) *Package {142 // include tells parser.ParseDir which files to include.143 // That means the file must be in the build package's GoFiles, CgoFiles,144 // TestGoFiles or XTestGoFiles list only (no tag-ignored files, swig or145 // other non-Go files).146 include := func(info fs.FileInfo) bool {147 files := [][]string{pkg.GoFiles, pkg.CgoFiles, pkg.TestGoFiles, pkg.XTestGoFiles}148 for _, f := range files {149 for _, name := range f {150 if name == info.Name() {151 return true152 }153 }154 }155 return false156 }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 if len(pkgs) == 0 {163 log.Fatalf("no source-code package in directory %s", pkg.Dir)164 }165 astPkg := pkgs[pkg.Name]166167 // TODO: go/doc does not include typed constants in the constants168 // list, which is what we want. For instance, time.Sunday is of type169 // time.Weekday, so it is defined in the type but not in the170 // Consts list for the package. This prevents171 // go doc time.Sunday172 // from finding the symbol. Work around this for now, but we173 // should fix it in go/doc.174 // A similar story applies to factory functions.175 mode := doc.AllDecls176 if showSrc {177 mode |= doc.PreserveAST // See comment for Package.emit.178 }179 var allGoFiles []*ast.File180 for _, p := range pkgs {181 for _, f := range p.Files {182 allGoFiles = append(allGoFiles, f)183 }184 }185 docPkg, err := doc.NewFromFiles(fset, allGoFiles, pkg.ImportPath, mode)186 if err != nil {187 log.Fatal(err)188 }189 typedValue := make(map[*doc.Value]bool)190 constructor := make(map[*doc.Func]bool)191 for _, typ := range docPkg.Types {192 docPkg.Consts = append(docPkg.Consts, typ.Consts...)193 docPkg.Vars = append(docPkg.Vars, typ.Vars...)194 docPkg.Funcs = append(docPkg.Funcs, typ.Funcs...)195 if isExported(typ.Name) {196 for _, value := range typ.Consts {197 typedValue[value] = true198 }199 for _, value := range typ.Vars {200 typedValue[value] = true201 }202 for _, fun := range typ.Funcs {203 // We don't count it as a constructor bound to the type204 // if the type itself is not exported.205 constructor[fun] = true206 }207 }208 }209210 p := &Package{211 writer: writer,212 name: pkg.Name,213 userPath: userPath,214 pkg: astPkg,215 file: ast.MergePackageFiles(astPkg, 0),216 doc: docPkg,217 typedValue: typedValue,218 constructor: constructor,219 build: pkg,220 fs: fset,221 }222 p.buf.pkg = p223 return p224}225226func (pkg *Package) Printf(format string, args ...any) {227 fmt.Fprintf(&pkg.buf, format, args...)228}229230func (pkg *Package) flush() {231 _, err := pkg.writer.Write(pkg.buf.Bytes())232 if err != nil {233 log.Fatal(err)234 }235 pkg.buf.Reset() // Not needed, but it's a flush.236}237238var newlineBytes = []byte("\n\n") // We never ask for more than 2.239240// newlines guarantees there are n newlines at the end of the buffer.241func (pkg *Package) newlines(n int) {242 for !bytes.HasSuffix(pkg.buf.Bytes(), newlineBytes[:n]) {243 pkg.buf.WriteRune('\n')244 }245}246247// emit prints the node. If showSrc is true, it ignores the provided comment,248// assuming the comment is in the node itself. Otherwise, the go/doc package249// clears the stuff we don't want to print anyway. It's a bit of a magic trick.250func (pkg *Package) emit(comment string, node ast.Node) {251 if node != nil {252 var arg any = node253 if showSrc {254 // Need an extra little dance to get internal comments to appear.255 arg = &printer.CommentedNode{256 Node: node,257 Comments: pkg.file.Comments,258 }259 }260 err := format.Node(&pkg.buf, pkg.fs, arg)261 if err != nil {262 log.Fatal(err)263 }264 if comment != "" && !showSrc {265 pkg.newlines(1)266 pkg.ToText(&pkg.buf, comment, indent, indent+indent)267 pkg.newlines(2) // Blank line after comment to separate from next item.268 } else {269 pkg.newlines(1)270 }271 }272}273274// oneLineNode returns a one-line summary of the given input node.275func (pkg *Package) oneLineNode(node ast.Node) string {276 const maxDepth = 10277 return pkg.oneLineNodeDepth(node, maxDepth)278}279280// oneLineNodeDepth returns a one-line summary of the given input node.281// The depth specifies the maximum depth when traversing the AST.282func (pkg *Package) oneLineNodeDepth(node ast.Node, depth int) string {283 const dotDotDot = "..."284 if depth == 0 {285 return dotDotDot286 }287 depth--288289 switch n := node.(type) {290 case nil:291 return ""292293 case *ast.GenDecl:294 // Formats const and var declarations.295 trailer := ""296 if len(n.Specs) > 1 {297 trailer = " " + dotDotDot298 }299300 // Find the first relevant spec.301 typ := ""302 for i, spec := range n.Specs {303 valueSpec := spec.(*ast.ValueSpec) // Must succeed; we can't mix types in one GenDecl.304305 // The type name may carry over from a previous specification in the306 // case of constants and iota.307 if valueSpec.Type != nil {308 typ = fmt.Sprintf(" %s", pkg.oneLineNodeDepth(valueSpec.Type, depth))309 } else if len(valueSpec.Values) > 0 {310 typ = ""311 }312313 if !isExported(valueSpec.Names[0].Name) {314 continue315 }316 val := ""317 if i < len(valueSpec.Values) && valueSpec.Values[i] != nil {318 val = fmt.Sprintf(" = %s", pkg.oneLineNodeDepth(valueSpec.Values[i], depth))319 }320 return fmt.Sprintf("%s %s%s%s%s", n.Tok, valueSpec.Names[0], typ, val, trailer)321 }322 return ""323324 case *ast.FuncDecl:325 // Formats func declarations.326 name := n.Name.Name327 recv := pkg.oneLineNodeDepth(n.Recv, depth)328 if len(recv) > 0 {329 recv = "(" + recv + ") "330 }331 fnc := pkg.oneLineNodeDepth(n.Type, depth)332 fnc = strings.TrimPrefix(fnc, "func")333 return fmt.Sprintf("func %s%s%s", recv, name, fnc)334335 case *ast.TypeSpec:336 sep := " "337 if n.Assign.IsValid() {338 sep = " = "339 }340 tparams := pkg.formatTypeParams(n.TypeParams, depth)341 return fmt.Sprintf("type %s%s%s%s", n.Name.Name, tparams, sep, pkg.oneLineNodeDepth(n.Type, depth))342343 case *ast.FuncType:344 var params []string345 if n.Params != nil {346 for _, field := range n.Params.List {347 params = append(params, pkg.oneLineField(field, depth))348 }349 }350 needParens := false351 var results []string352 if n.Results != nil {353 needParens = needParens || len(n.Results.List) > 1354 for _, field := range n.Results.List {355 needParens = needParens || len(field.Names) > 0356 results = append(results, pkg.oneLineField(field, depth))357 }358 }359360 tparam := pkg.formatTypeParams(n.TypeParams, depth)361 param := joinStrings(params)362 if len(results) == 0 {363 return fmt.Sprintf("func%s(%s)", tparam, param)364 }365 result := joinStrings(results)366 if !needParens {367 return fmt.Sprintf("func%s(%s) %s", tparam, param, result)368 }369 return fmt.Sprintf("func%s(%s) (%s)", tparam, param, result)370371 case *ast.StructType:372 if n.Fields == nil || len(n.Fields.List) == 0 {373 return "struct{}"374 }375 return "struct{ ... }"376377 case *ast.InterfaceType:378 if n.Methods == nil || len(n.Methods.List) == 0 {379 return "interface{}"380 }381 return "interface{ ... }"382383 case *ast.FieldList:384 if n == nil || len(n.List) == 0 {385 return ""386 }387 if len(n.List) == 1 {388 return pkg.oneLineField(n.List[0], depth)389 }390 return dotDotDot391392 case *ast.FuncLit:393 return pkg.oneLineNodeDepth(n.Type, depth) + " { ... }"394395 case *ast.CompositeLit:396 typ := pkg.oneLineNodeDepth(n.Type, depth)397 if len(n.Elts) == 0 {398 return fmt.Sprintf("%s{}", typ)399 }400 return fmt.Sprintf("%s{ %s }", typ, dotDotDot)401402 case *ast.ArrayType:403 length := pkg.oneLineNodeDepth(n.Len, depth)404 element := pkg.oneLineNodeDepth(n.Elt, depth)405 return fmt.Sprintf("[%s]%s", length, element)406407 case *ast.MapType:408 key := pkg.oneLineNodeDepth(n.Key, depth)409 value := pkg.oneLineNodeDepth(n.Value, depth)410 return fmt.Sprintf("map[%s]%s", key, value)411412 case *ast.CallExpr:413 fnc := pkg.oneLineNodeDepth(n.Fun, depth)414 var args []string415 for _, arg := range n.Args {416 args = append(args, pkg.oneLineNodeDepth(arg, depth))417 }418 return fmt.Sprintf("%s(%s)", fnc, joinStrings(args))419420 case *ast.UnaryExpr:421 return fmt.Sprintf("%s%s", n.Op, pkg.oneLineNodeDepth(n.X, depth))422423 case *ast.Ident:424 return n.Name425426 default:427 // As a fallback, use default formatter for all unknown node types.428 buf := new(strings.Builder)429 format.Node(buf, pkg.fs, node)430 s := buf.String()431 if strings.Contains(s, "\n") {432 return dotDotDot433 }434 return s435 }436}437438func (pkg *Package) formatTypeParams(list *ast.FieldList, depth int) string {439 if list.NumFields() == 0 {440 return ""441 }442 var tparams []string443 for _, field := range list.List {444 tparams = append(tparams, pkg.oneLineField(field, depth))445 }446 return "[" + joinStrings(tparams) + "]"447}448449// oneLineField returns a one-line summary of the field.450func (pkg *Package) oneLineField(field *ast.Field, depth int) string {451 var names []string452 for _, name := range field.Names {453 names = append(names, name.Name)454 }455 if len(names) == 0 {456 return pkg.oneLineNodeDepth(field.Type, depth)457 }458 return joinStrings(names) + " " + pkg.oneLineNodeDepth(field.Type, depth)459}460461// joinStrings formats the input as a comma-separated list,462// but truncates the list at some reasonable length if necessary.463func joinStrings(ss []string) string {464 var n int465 for i, s := range ss {466 n += len(s) + len(", ")467 if n > punchedCardWidth {468 ss = append(ss[:i:i], "...")469 break470 }471 }472 return strings.Join(ss, ", ")473}474475// printHeader prints a header for the section named s, adding a blank line on each side.476func (pkg *Package) printHeader(s string) {477 pkg.Printf("\n%s\n\n", s)478}479480// constsDoc prints all const documentation, if any, including a header.481// The one argument is the valueDoc registry.482func (pkg *Package) constsDoc(printed map[*ast.GenDecl]bool) {483 var header bool484 for _, value := range pkg.doc.Consts {485 // Constants and variables come in groups, and valueDoc prints486 // all the items in the group. We only need to find one exported symbol.487 for _, name := range value.Names {488 if isExported(name) && !pkg.typedValue[value] {489 if !header {490 pkg.printHeader("CONSTANTS")491 header = true492 }493 pkg.valueDoc(value, printed)494 break495 }496 }497 }498}499500// varsDoc prints all var documentation, if any, including a header.501// Printed is the valueDoc registry.502func (pkg *Package) varsDoc(printed map[*ast.GenDecl]bool) {503 var header bool504 for _, value := range pkg.doc.Vars {505 // Constants and variables come in groups, and valueDoc prints506 // all the items in the group. We only need to find one exported symbol.507 for _, name := range value.Names {508 if isExported(name) && !pkg.typedValue[value] {509 if !header {510 pkg.printHeader("VARIABLES")511 header = true512 }513 pkg.valueDoc(value, printed)514 break515 }516 }517 }518}519520// funcsDoc prints all func documentation, if any, including a header.521func (pkg *Package) funcsDoc() {522 var header bool523 for _, fun := range pkg.doc.Funcs {524 if isExported(fun.Name) && !pkg.constructor[fun] {525 if !header {526 pkg.printHeader("FUNCTIONS")527 header = true528 }529 pkg.emit(fun.Doc, fun.Decl)530 }531 }532}533534// typesDoc prints all type documentation, if any, including a header.535func (pkg *Package) typesDoc() {536 var header bool537 for _, typ := range pkg.doc.Types {538 if isExported(typ.Name) {539 if !header {540 pkg.printHeader("TYPES")541 header = true542 }543 pkg.typeDoc(typ)544 }545 }546}547548// packageDoc prints the docs for the package.549func (pkg *Package) packageDoc() {550 pkg.Printf("") // Trigger the package clause; we know the package exists.551 if showAll || !short {552 pkg.ToText(&pkg.buf, pkg.doc.Doc, "", indent)553 pkg.newlines(1)554 }555556 switch {557 case showAll:558 printed := make(map[*ast.GenDecl]bool) // valueDoc registry559 pkg.constsDoc(printed)560 pkg.varsDoc(printed)561 pkg.funcsDoc()562 pkg.typesDoc()563564 case pkg.pkg.Name == "main" && !showCmd:565 // Show only package docs for commands.566 return567568 default:569 if !short {570 pkg.newlines(2) // Guarantee blank line before the components.571 }572 pkg.valueSummary(pkg.doc.Consts, false)573 pkg.valueSummary(pkg.doc.Vars, false)574 pkg.funcSummary(pkg.doc.Funcs, false)575 pkg.typeSummary()576 pkg.exampleSummary(pkg.doc.Examples, false)577 }578579 if !short {580 pkg.bugs()581 }582}583584// packageClause prints the package clause.585func (pkg *Package) packageClause() {586 if short {587 return588 }589 importPath := pkg.build.ImportComment590 if importPath == "" {591 importPath = pkg.build.ImportPath592 }593594 // If we're using modules, the import path derived from module code locations wins.595 // If we did a file system scan, we knew the import path when we found the directory.596 // But if we started with a directory name, we never knew the import path.597 // Either way, we don't know it now, and it's cheap to (re)compute it.598 if usingModules {599 for _, root := range codeRoots() {600 if pkg.build.Dir == root.dir {601 importPath = root.importPath602 break603 }604 if strings.HasPrefix(pkg.build.Dir, root.dir+string(filepath.Separator)) {605 suffix := filepath.ToSlash(pkg.build.Dir[len(root.dir)+1:])606 if root.importPath == "" {607 importPath = suffix608 } else {609 importPath = root.importPath + "/" + suffix610 }611 break612 }613 }614 }615616 pkg.Printf("package %s // import %q\n\n", pkg.name, importPath)617 if !usingModules && importPath != pkg.build.ImportPath {618 pkg.Printf("WARNING: package source is installed in %q\n", pkg.build.ImportPath)619 }620}621622// valueSummary prints a one-line summary for each set of values and constants.623// If all the types in a constant or variable declaration belong to the same624// type they can be printed by typeSummary, and so can be suppressed here.625func (pkg *Package) valueSummary(values []*doc.Value, showGrouped bool) {626 var isGrouped map[*doc.Value]bool627 if !showGrouped {628 isGrouped = make(map[*doc.Value]bool)629 for _, typ := range pkg.doc.Types {630 if !isExported(typ.Name) {631 continue632 }633 for _, c := range typ.Consts {634 isGrouped[c] = true635 }636 for _, v := range typ.Vars {637 isGrouped[v] = true638 }639 }640 }641642 for _, value := range values {643 if !isGrouped[value] {644 if decl := pkg.oneLineNode(value.Decl); decl != "" {645 pkg.Printf("%s\n", decl)646 }647 }648 }649}650651// funcSummary prints a one-line summary for each function. Constructors652// are printed by typeSummary, below, and so can be suppressed here.653func (pkg *Package) funcSummary(funcs []*doc.Func, showConstructors bool) {654 for _, fun := range funcs {655 // Exported functions only. The go/doc package does not include methods here.656 if isExported(fun.Name) {657 if showConstructors || !pkg.constructor[fun] {658 pkg.Printf("%s\n", pkg.oneLineNode(fun.Decl))659 if showEx {660 pkg.exampleSummary(fun.Examples, false)661 }662 }663 }664 }665}666667// exampleSummary prints a one-line summary for each example.668func (pkg *Package) exampleSummary(exs []*doc.Example, showDoc bool) {669 if !showEx {670 return671 }672 for _, ex := range exs {673 pkg.Printf(indent+"func Example%s()\n", ex.Name)674 if showDoc && ex.Doc != "" {675 pkg.ToText(&pkg.buf, ex.Doc, indent+indent, indent+indent)676 }677 }678}679680// typeSummary prints a one-line summary for each type, followed by its constructors.681func (pkg *Package) typeSummary() {682 for _, typ := range pkg.doc.Types {683 for _, spec := range typ.Decl.Specs {684 typeSpec := spec.(*ast.TypeSpec) // Must succeed.685 if isExported(typeSpec.Name.Name) {686 pkg.Printf("%s\n", pkg.oneLineNode(typeSpec))687 // Now print the consts, vars, and constructors.688 for _, c := range typ.Consts {689 if decl := pkg.oneLineNode(c.Decl); decl != "" {690 pkg.Printf(indent+"%s\n", decl)691 }692 }693 for _, v := range typ.Vars {694 if decl := pkg.oneLineNode(v.Decl); decl != "" {695 pkg.Printf(indent+"%s\n", decl)696 }697 }698 for _, constructor := range typ.Funcs {699 if isExported(constructor.Name) {700 pkg.Printf(indent+"%s\n", pkg.oneLineNode(constructor.Decl))701 pkg.exampleSummary(constructor.Examples, false)702 }703 }704 pkg.exampleSummary(typ.Examples, false)705 }706 }707 }708}709710// bugs prints the BUGS information for the package.711// TODO: Provide access to TODOs and NOTEs as well (very noisy so off by default)?712func (pkg *Package) bugs() {713 if pkg.doc.Notes["BUG"] == nil {714 return715 }716 pkg.Printf("\n")717 for _, note := range pkg.doc.Notes["BUG"] {718 pkg.Printf("%s: %v\n", "BUG", note.Body)719 }720}721722// findValues finds the doc.Values that describe the symbol.723func (pkg *Package) findValues(symbol string, docValues []*doc.Value) (values []*doc.Value) {724 for _, value := range docValues {725 for _, name := range value.Names {726 if match(symbol, name) {727 values = append(values, value)728 }729 }730 }731 return732}733734// findFuncs finds the doc.Funcs that describes the symbol.735func (pkg *Package) findFuncs(symbol string) (funcs []*doc.Func) {736 for _, fun := range pkg.doc.Funcs {737 if match(symbol, fun.Name) {738 funcs = append(funcs, fun)739 }740 }741 return742}743744// findTypes finds the doc.Types that describes the symbol.745// If symbol is empty, it finds all exported types.746func (pkg *Package) findTypes(symbol string) (types []*doc.Type) {747 for _, typ := range pkg.doc.Types {748 if symbol == "" && isExported(typ.Name) || match(symbol, typ.Name) {749 types = append(types, typ)750 }751 }752 return753}754755// findExamples finds any examples that describe the symbol.756func (pkg *Package) findExamples(symbol string) (examples []*doc.Example) {757 if !strings.HasPrefix(symbol, "Example") {758 return759 }760 symbol = strings.TrimPrefix(symbol, "Example")761 var all []*doc.Example762 all = append(all, pkg.doc.Examples...)763 for _, typ := range pkg.doc.Types {764 all = append(all, typ.Examples...)765 for _, fun := range typ.Funcs {766 all = append(all, fun.Examples...)767 }768 for _, fun := range typ.Methods {769 all = append(all, fun.Examples...)770 }771 }772 for _, fun := range pkg.doc.Funcs {773 all = append(all, fun.Examples...)774 }775776 // always include unexported in below match(), so Example_one777 // is still matched despite trimming the Example_ part.778 u := unexported779 unexported = true780 for _, ex := range all {781 if match(symbol, ex.Name) {782 examples = append(examples, ex)783 }784 }785 unexported = u786 return787}788789// findTypeSpec returns the ast.TypeSpec within the declaration that defines the symbol.790// The name must match exactly.791func (pkg *Package) findTypeSpec(decl *ast.GenDecl, symbol string) *ast.TypeSpec {792 for _, spec := range decl.Specs {793 typeSpec := spec.(*ast.TypeSpec) // Must succeed.794 if symbol == typeSpec.Name.Name {795 return typeSpec796 }797 }798 return nil799}800801// symbolDoc prints the docs for symbol. There may be multiple matches.802// If symbol matches a type, output includes its methods factories and associated constants.803// If there is no top-level symbol, symbolDoc looks for methods that match.804func (pkg *Package) symbolDoc(symbol string) bool {805 found := false806 // Functions.807 for _, fun := range pkg.findFuncs(symbol) {808 // Symbol is a function.809 decl := fun.Decl810 found = true811 if short {812 pkg.Printf("%s\n", pkg.oneLineNode(decl))813 pkg.exampleSummary(fun.Examples, false)814 continue815 }816 pkg.emit(fun.Doc, decl)817 pkg.exampleSummary(fun.Examples, true)818 }819 for _, ex := range pkg.findExamples(symbol) {820 pkg.emitExample(ex)821 found = true822 }823 // Constants and variables behave the same.824 values := pkg.findValues(symbol, pkg.doc.Consts)825 values = append(values, pkg.findValues(symbol, pkg.doc.Vars)...)826 printed := make(map[*ast.GenDecl]bool) // valueDoc registry827 for _, value := range values {828 pkg.valueDoc(value, printed)829 found = true830 }831 // Types.832 for _, typ := range pkg.findTypes(symbol) {833 pkg.typeDoc(typ)834 found = true835 }836 if !found {837 // See if there are methods.838 if !pkg.printMethodDoc("", symbol) {839 return false840 }841 }842 return true843}844845// valueDoc prints the docs for a constant or variable. The printed map records846// which values have been printed already to avoid duplication. Otherwise, a847// declaration like:848//849// const ( c = 1; C = 2 )850//851// … could be printed twice if the -u flag is set, as it matches twice.852func (pkg *Package) valueDoc(value *doc.Value, printed map[*ast.GenDecl]bool) {853 if printed[value.Decl] {854 return855 }856 // Print each spec only if there is at least one exported symbol in it.857 // (See issue 11008.)858 // TODO: Should we elide unexported symbols from a single spec?859 // It's an unlikely scenario, probably not worth the trouble.860 // TODO: Would be nice if go/doc did this for us.861 specs := make([]ast.Spec, 0, len(value.Decl.Specs))862 var typ ast.Expr863 for _, spec := range value.Decl.Specs {864 vspec := spec.(*ast.ValueSpec)865866 // The type name may carry over from a previous specification in the867 // case of constants and iota.868 if vspec.Type != nil {869 typ = vspec.Type870 }871872 for _, ident := range vspec.Names {873 if showSrc || isExported(ident.Name) {874 if vspec.Type == nil && vspec.Values == nil && typ != nil {875 // This a standalone identifier, as in the case of iota usage.876 // Thus, assume the type comes from the previous type.877 vspec.Type = &ast.Ident{878 Name: pkg.oneLineNode(typ),879 NamePos: vspec.End() - 1,880 }881 }882883 specs = append(specs, vspec)884 typ = nil // Only inject type on first exported identifier885 break886 }887 }888 }889 if len(specs) == 0 {890 return891 }892 value.Decl.Specs = specs893 printed[value.Decl] = true894 if short {895 pkg.Printf("%s\n", pkg.oneLineNode(value.Decl))896 return897 }898 pkg.emit(value.Doc, value.Decl)899}900901// typeDoc prints the docs for a type, including constructors and other items902// related to it.903func (pkg *Package) typeDoc(typ *doc.Type) {904 decl := typ.Decl905 spec := pkg.findTypeSpec(decl, typ.Name)906 trimUnexportedElems(spec)907 // If there are multiple types defined, reduce to just this one.908 if len(decl.Specs) > 1 {909 decl.Specs = []ast.Spec{spec}910 }911 if short {912 pkg.Printf("%s\n", pkg.oneLineNode(spec))913 return914 }915 pkg.emit(typ.Doc, decl)916 pkg.newlines(2)917 // Show associated methods, constants, etc.918 if showAll {919 printed := make(map[*ast.GenDecl]bool) // valueDoc registry920 // We can use append here to print consts, then vars. Ditto for funcs and methods.921 values := typ.Consts922 values = append(values, typ.Vars...)923 for _, value := range values {924 for _, name := range value.Names {925 if isExported(name) {926 pkg.valueDoc(value, printed)927 break928 }929 }930 }931 funcs := typ.Funcs932 funcs = append(funcs, typ.Methods...)933 for _, fun := range funcs {934 if isExported(fun.Name) {935 pkg.emit(fun.Doc, fun.Decl)936 if fun.Doc == "" {937 pkg.newlines(2)938 }939 }940 }941 } else {942 pkg.valueSummary(typ.Consts, true)943 pkg.valueSummary(typ.Vars, true)944 pkg.funcSummary(typ.Funcs, true)945 pkg.exampleSummary(typ.Examples, false)946 pkg.funcSummary(typ.Methods, true)947 }948}949950// trimUnexportedElems modifies spec in place to elide unexported fields from951// structs and methods from interfaces (unless the unexported flag is set or we952// are asked to show the original source).953func trimUnexportedElems(spec *ast.TypeSpec) {954 if showSrc {955 return956 }957 switch typ := spec.Type.(type) {958 case *ast.StructType:959 typ.Fields = trimUnexportedFields(typ.Fields, false)960 case *ast.InterfaceType:961 typ.Methods = trimUnexportedFields(typ.Methods, true)962 }963}964965// trimUnexportedFields returns the field list trimmed of unexported fields.966func trimUnexportedFields(fields *ast.FieldList, isInterface bool) *ast.FieldList {967 what := "methods"968 if !isInterface {969 what = "fields"970 }971972 trimmed := false973 list := make([]*ast.Field, 0, len(fields.List))974 for _, field := range fields.List {975 // When printing fields we normally print field.Doc.976 // Here we are going to pass the AST to go/format,977 // which will print the comments from the AST,978 // not field.Doc which is from go/doc.979 // The two are similar but not identical;980 // for example, field.Doc does not include directives.981 // In order to consistently print field.Doc,982 // we replace the comment in the AST with field.Doc.983 // That will cause go/format to print what we want.984 // See issue #56592.985 if field.Doc != nil {986 doc := field.Doc987 text := doc.Text()988989 trailingBlankLine := len(doc.List[len(doc.List)-1].Text) == 2990 if !trailingBlankLine {991 // Remove trailing newline.992 lt := len(text)993 if lt > 0 && text[lt-1] == '\n' {994 text = text[:lt-1]995 }996 }997998 start := doc.List[0].Slash999 doc.List = doc.List[:0]1000 for line := range strings.SplitSeq(text, "\n") {1001 prefix := "// "1002 if len(line) > 0 && line[0] == '\t' {1003 prefix = "//"1004 }1005 doc.List = append(doc.List, &ast.Comment{1006 Text: prefix + line,1007 })1008 }1009 doc.List[0].Slash = start1010 }10111012 names := field.Names1013 if len(names) == 0 {1014 // Embedded type. Use the name of the type. It must be of the form ident or1015 // pkg.ident (for structs and interfaces), or *ident or *pkg.ident (structs only).1016 // Or a type embedded in a constraint.1017 // Nothing else is allowed.1018 ty := field.Type1019 if se, ok := field.Type.(*ast.StarExpr); !isInterface && ok {1020 // The form *ident or *pkg.ident is only valid on1021 // embedded types in structs.1022 ty = se.X1023 }1024 constraint := false1025 switch ident := ty.(type) {1026 case *ast.Ident:1027 if isInterface && ident.Obj == nil &&1028 (ident.Name == "error" || ident.Name == "comparable") {1029 // For documentation purposes, we consider the builtin error1030 // and comparable types special when embedded in an interface,1031 // such that they always get shown publicly.1032 list = append(list, field)1033 continue1034 }1035 names = []*ast.Ident{ident}1036 case *ast.SelectorExpr:1037 // An embedded type may refer to a type in another package.1038 names = []*ast.Ident{ident.Sel}1039 default:1040 // An approximation or union or type1041 // literal in an interface.1042 constraint = true1043 }1044 if names == nil && !constraint {1045 // Can only happen if AST is incorrect. Safe to continue with a nil list.1046 log.Print("invalid program: unexpected type for embedded field")1047 }1048 }1049 // Trims if any is unexported. Good enough in practice.1050 ok := true1051 if !unexported {1052 for _, name := range names {1053 if !isExported(name.Name) {1054 trimmed = true1055 ok = false1056 break1057 }1058 }1059 }1060 if ok {1061 list = append(list, field)1062 }1063 }1064 if !trimmed {1065 return fields1066 }1067 unexportedField := &ast.Field{1068 Type: &ast.Ident{1069 // Hack: printer will treat this as a field with a named type.1070 // Setting Name and NamePos to ("", fields.Closing-1) ensures that1071 // when Pos and End are called on this field, they return the1072 // position right before closing '}' character.1073 Name: "",1074 NamePos: fields.Closing - 1,1075 },1076 Comment: &ast.CommentGroup{1077 List: []*ast.Comment{{Text: fmt.Sprintf("// Has unexported %s.\n", what)}},1078 },1079 }1080 return &ast.FieldList{1081 Opening: fields.Opening,1082 List: append(list, unexportedField),1083 Closing: fields.Closing,1084 }1085}10861087// printMethodDoc prints the docs for matches of symbol.method.1088// If symbol is empty, it prints all methods for any concrete type1089// that match the name. It reports whether it found any methods.1090func (pkg *Package) printMethodDoc(symbol, method string) bool {1091 types := pkg.findTypes(symbol)1092 if types == nil {1093 if symbol == "" {1094 return false1095 }1096 pkg.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath)1097 }1098 found := false1099 for _, typ := range types {1100 if len(typ.Methods) > 0 {1101 for _, meth := range typ.Methods {1102 if match(method, meth.Name) {1103 decl := meth.Decl1104 pkg.emit(meth.Doc, decl)1105 pkg.exampleSummary(meth.Examples, true)1106 found = true1107 }1108 }1109 continue1110 }1111 if symbol == "" {1112 continue1113 }1114 // Type may be an interface. The go/doc package does not attach1115 // an interface's methods to the doc.Type. We need to dig around.1116 spec := pkg.findTypeSpec(typ.Decl, typ.Name)1117 inter, ok := spec.Type.(*ast.InterfaceType)1118 if !ok {1119 // Not an interface type.1120 continue1121 }11221123 // Collect and print only the methods that match.1124 var methods []*ast.Field1125 for _, iMethod := range inter.Methods.List {1126 // This is an interface, so there can be only one name.1127 // TODO: Anonymous methods (embedding)1128 if len(iMethod.Names) == 0 {1129 continue1130 }1131 name := iMethod.Names[0].Name1132 if match(method, name) {1133 methods = append(methods, iMethod)1134 found = true1135 }1136 }1137 if found {1138 pkg.Printf("type %s ", spec.Name)1139 inter.Methods.List, methods = methods, inter.Methods.List1140 err := format.Node(&pkg.buf, pkg.fs, inter)1141 if err != nil {1142 log.Fatal(err)1143 }1144 pkg.newlines(1)1145 // Restore the original methods.1146 inter.Methods.List = methods1147 }1148 }1149 return found1150}11511152// printFieldDoc prints the docs for matches of symbol.fieldName.1153// It reports whether it found any field.1154// Both symbol and fieldName must be non-empty or it returns false.1155func (pkg *Package) printFieldDoc(symbol, fieldName string) bool {1156 if symbol == "" || fieldName == "" {1157 return false1158 }1159 types := pkg.findTypes(symbol)1160 if types == nil {1161 pkg.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath)1162 }1163 found := false1164 numUnmatched := 01165 for _, typ := range types {1166 // Type must be a struct.1167 spec := pkg.findTypeSpec(typ.Decl, typ.Name)1168 structType, ok := spec.Type.(*ast.StructType)1169 if !ok {1170 // Not a struct type.1171 continue1172 }1173 for _, field := range structType.Fields.List {1174 // TODO: Anonymous fields.1175 for _, name := range field.Names {1176 if !match(fieldName, name.Name) {1177 numUnmatched++1178 continue1179 }1180 if !found {1181 pkg.Printf("type %s struct {\n", typ.Name)1182 }1183 if field.Doc != nil {1184 // To present indented blocks in comments correctly, process the comment as1185 // a unit before adding the leading // to each line.1186 docBuf := new(bytes.Buffer)1187 pkg.ToText(docBuf, field.Doc.Text(), "", indent)1188 for line := range bytes.Lines(docBuf.Bytes()) {1189 line = bytes.TrimSuffix(line, []byte{'\n'})1190 fmt.Fprintf(&pkg.buf, "%s// %s\n", indent, line)1191 }1192 }1193 s := pkg.oneLineNode(field.Type)1194 lineComment := ""1195 if field.Comment != nil {1196 lineComment = fmt.Sprintf(" %s", field.Comment.List[0].Text)1197 }1198 pkg.Printf("%s%s %s%s\n", indent, name, s, lineComment)1199 found = true1200 }1201 }1202 }1203 if found {1204 if numUnmatched > 0 {1205 pkg.Printf("\n // ... other fields elided ...\n")1206 }1207 pkg.Printf("}\n")1208 }1209 return found1210}12111212// match reports whether the user's symbol matches the program's.1213// A lower-case character in the user's string matches either case in the program's.1214// The program string must be exported.1215func match(user, program string) bool {1216 if !isExported(program) {1217 return false1218 }1219 if matchCase {1220 return user == program1221 }1222 for _, u := range user {1223 p, w := utf8.DecodeRuneInString(program)1224 program = program[w:]1225 if u == p {1226 continue1227 }1228 if unicode.IsLower(u) && simpleFold(u) == simpleFold(p) {1229 continue1230 }1231 return false1232 }1233 return program == ""1234}12351236// simpleFold returns the minimum rune equivalent to r1237// under Unicode-defined simple case folding.1238func simpleFold(r rune) rune {1239 for {1240 r1 := unicode.SimpleFold(r)1241 if r1 <= r {1242 return r1 // wrapped around, found min1243 }1244 r = r11245 }1246}12471248// emitExample prints an example and its output.1249func (pkg *Package) emitExample(ex *doc.Example) {1250 pkg.buf.printed = true // omit the package clause1251 var err error1252 if ex.Play != nil {1253 err = format.Node(&pkg.buf, pkg.fs, ex.Play)1254 } else {1255 // If code is an *ast.BlockStmt, trim the braces and indentation1256 // by just printing the enclosed List of ast.[]Stmt.1257 b, ok := ex.Code.(*ast.BlockStmt)1258 if !ok {1259 err = format.Node(&pkg.buf, pkg.fs, ex.Code)1260 } else {1261 err = format.Node(&pkg.buf, pkg.fs, b.List)1262 }1263 }1264 if err != nil {1265 log.Fatal(err)1266 }1267 if ex.Output != "" {1268 pkg.newlines(2)1269 pkg.Printf("Output: ")1270 if strings.Count(ex.Output, "\n") > 1 {1271 pkg.newlines(1)1272 }1273 pkg.Printf("%s", ex.Output)1274 }1275 pkg.newlines(1)1276}
Findings
✓ No findings reported for this file.