From 101a677ebf87f6ed0ed877533c54c7270edadc20 Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Sat, 13 Oct 2018 21:06:43 +1100 Subject: [PATCH] cmd/doc: add -all flag to print all documentation for package Unlike the one for the old godoc, you need the -u flag to see unexported symbols. This seems like the right behavior: it's consistent. For now at least, the argument must be a package, not a symbol. This is also different from old godoc. Required a little refactoring but also cleaned up a few things. Update #25595 Leaving the bug open for now until we tackle go doc -all symbol Change-Id: Ibc1975bfa592cb1e92513eb2e5e9e11e01a60095 Reviewed-on: https://go-review.googlesource.com/c/141977 Run-TryBot: Rob Pike TryBot-Result: Gobot Gobot Reviewed-by: Russ Cox --- src/cmd/doc/doc_test.go | 116 +++++++++++++- src/cmd/doc/main.go | 14 ++ src/cmd/doc/pkg.go | 283 ++++++++++++++++++++++----------- src/cmd/go/alldocs.go | 2 + src/cmd/go/internal/doc/doc.go | 2 + 5 files changed, 326 insertions(+), 91 deletions(-) diff --git a/src/cmd/doc/doc_test.go b/src/cmd/doc/doc_test.go index 64b1fb596b..80fdb5c72e 100644 --- a/src/cmd/doc/doc_test.go +++ b/src/cmd/doc/doc_test.go @@ -147,6 +147,69 @@ var tests = []test{ `type T1 T2`, // Type alias does not display as type declaration. }, }, + // Package dump -all + { + "full package", + []string{"-all", p}, + []string{ + `package pkg .*import`, + `Package comment`, + `CONSTANTS`, + `Comment before ConstOne`, + `ConstOne = 1`, + `ConstTwo = 2 // Comment on line with ConstTwo`, + `ConstFive`, + `ConstSix`, + `Const block where first entry is unexported`, + `ConstLeft2, constRight2 uint64`, + `constLeft3, ConstRight3`, + `ConstLeft4, ConstRight4`, + `Duplicate = iota`, + `const CaseMatch = 1`, + `const Casematch = 2`, + `const ExportedConstant = 1`, + `const MultiLineConst = `, + `MultiLineString1`, + `VARIABLES`, + `Comment before VarOne`, + `VarOne = 1`, + `Comment about block of variables`, + `VarFive = 5`, + `var ExportedVariable = 1`, + `var LongLine = newLongLine\(`, + `var MultiLineVar = map\[struct {`, + `FUNCTIONS`, + `func ExportedFunc\(a int\) bool`, + `Comment about exported function`, + `func MultiLineFunc\(x interface`, + `func ReturnUnexported\(\) unexportedType`, + `TYPES`, + `type ExportedInterface interface`, + `type ExportedStructOneField struct`, + `type ExportedType struct`, + `Comment about exported type`, + `const ConstGroup4 ExportedType = ExportedType`, + `ExportedTypedConstant ExportedType = iota`, + `Constants tied to ExportedType`, + `func ExportedTypeConstructor\(\) \*ExportedType`, + `Comment about constructor for exported type`, + `func ReturnExported\(\) ExportedType`, + `func \(ExportedType\) ExportedMethod\(a int\) bool`, + `Comment about exported method`, + `type T1 = T2`, + `type T2 int`, + }, + []string{ + `constThree`, + `_, _ uint64 = 2 \* iota, 1 << iota`, + `constLeft1, constRight1`, + `duplicate`, + `varFour`, + `func internalFunc`, + `unexportedField`, + `func \(unexportedType\)`, + }, + }, // Package dump -u { "full package with u", @@ -164,6 +227,58 @@ var tests = []test{ `MultiLine(String|Method|Field)`, // No data from multi line portions. }, }, + // Package dump -u -all + { + "full package", + []string{"-u", "-all", p}, + []string{ + `package pkg .*import`, + `Package comment`, + `CONSTANTS`, + `Comment before ConstOne`, + `ConstOne += 1`, + `ConstTwo += 2 // Comment on line with ConstTwo`, + `constThree = 3 // Comment on line with constThree`, + `ConstFive`, + `const internalConstant += 2`, + `Comment about internal constant`, + `VARIABLES`, + `Comment before VarOne`, + `VarOne += 1`, + `Comment about block of variables`, + `varFour += 4`, + `VarFive += 5`, + `varSix += 6`, + `var ExportedVariable = 1`, + `var LongLine = newLongLine\(`, + `var MultiLineVar = map\[struct {`, + `var internalVariable = 2`, + `Comment about internal variable`, + `FUNCTIONS`, + `func ExportedFunc\(a int\) bool`, + `Comment about exported function`, + `func MultiLineFunc\(x interface`, + `func internalFunc\(a int\) bool`, + `Comment about internal function`, + `func newLongLine\(ss .*string\)`, + `TYPES`, + `type ExportedType struct`, + `type T1 = T2`, + `type T2 int`, + `type unexportedType int`, + `Comment about unexported type`, + `ConstGroup1 unexportedType = iota`, + `ConstGroup2`, + `ConstGroup3`, + `ExportedTypedConstant_unexported unexportedType = iota`, + `Constants tied to unexportedType`, + `const unexportedTypedConstant unexportedType = 1`, + `func ReturnUnexported\(\) unexportedType`, + `func \(unexportedType\) ExportedMethod\(\) bool`, + `func \(unexportedType\) unexportedMethod\(\) bool`, + }, + nil, + }, // Single constant. { @@ -361,7 +476,6 @@ var tests = []test{ `io.Reader.*Comment on line with embedded Reader`, }, []string{ - `int.*embedded`, // No unexported embedded field. `Comment about exported method`, // No comment about exported method. `unexportedMethod`, // No unexported method. `unexportedTypedConstant`, // No unexported constant. diff --git a/src/cmd/doc/main.go b/src/cmd/doc/main.go index a3e09d3f87..614f19438c 100644 --- a/src/cmd/doc/main.go +++ b/src/cmd/doc/main.go @@ -31,6 +31,9 @@ // The -src flag causes doc to print the full source code for the symbol, such // as the body of a struct, function or method. // +// The -all flag causes doc to print all documentation for the package and +// all its visible symbols. The argument must identify a package. +// // For complete documentation, run "go help doc". package main @@ -52,6 +55,7 @@ import ( var ( unexported bool // -u flag matchCase bool // -c flag + showAll bool // -all flag showCmd bool // -cmd flag showSrc bool // -src flag ) @@ -88,6 +92,7 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) { matchCase = false flagSet.BoolVar(&unexported, "u", false, "show unexported symbols as well as exported") flagSet.BoolVar(&matchCase, "c", false, "symbol matching honors case (paths not affected)") + flagSet.BoolVar(&showAll, "all", false, "show all documentation for package") flagSet.BoolVar(&showCmd, "cmd", false, "show symbols with package docs even if package is a command") flagSet.BoolVar(&showSrc, "src", false, "show source code for symbol") flagSet.Parse(args) @@ -127,6 +132,15 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) { unexported = true } + // We have a package. + if showAll { + if symbol != "" { + return fmt.Errorf("-all valid only for package, not symbol: %s", symbol) + } + pkg.allDoc() + return + } + switch { case symbol == "": pkg.packageDoc() // The package exists, so we got some output. diff --git a/src/cmd/doc/pkg.go b/src/cmd/doc/pkg.go index 154fb7b45f..bb1b998537 100644 --- a/src/cmd/doc/pkg.go +++ b/src/cmd/doc/pkg.go @@ -29,15 +29,18 @@ const ( ) type Package struct { - writer io.Writer // Destination for output. - name string // Package name, json for encoding/json. - userPath string // String the user used to find this package. - pkg *ast.Package // Parsed package. - file *ast.File // Merged from all files in the package - doc *doc.Package - build *build.Package - fs *token.FileSet // Needed for printing. - buf bytes.Buffer + writer io.Writer // Destination for output. + name string // Package name, json for encoding/json. + userPath string // String the user used to find this package. + pkg *ast.Package // Parsed package. + file *ast.File // Merged from all files in the package + doc *doc.Package + build *build.Package + typedValue map[*doc.Value]bool // Consts and vars related to types. + constructor map[*doc.Func]bool // Constructors. + packageClausePrinted bool // Prevent repeated package clauses. + fs *token.FileSet // Needed for printing. + buf bytes.Buffer } type PackageError string // type returned by pkg.Fatalf. @@ -142,21 +145,38 @@ func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Packag mode |= doc.PreserveAST // See comment for Package.emit. } docPkg := doc.New(astPkg, pkg.ImportPath, mode) + typedValue := make(map[*doc.Value]bool) + constructor := make(map[*doc.Func]bool) for _, typ := range docPkg.Types { docPkg.Consts = append(docPkg.Consts, typ.Consts...) + for _, value := range typ.Consts { + typedValue[value] = true + } docPkg.Vars = append(docPkg.Vars, typ.Vars...) + for _, value := range typ.Vars { + typedValue[value] = true + } docPkg.Funcs = append(docPkg.Funcs, typ.Funcs...) + for _, fun := range typ.Funcs { + // We don't count it as a constructor bound to the type + // if the type itself is not exported. + if isExported(typ.Name) { + constructor[fun] = true + } + } } return &Package{ - writer: writer, - name: pkg.Name, - userPath: userPath, - pkg: astPkg, - file: ast.MergePackageFiles(astPkg, 0), - doc: docPkg, - build: pkg, - fs: fs, + writer: writer, + name: pkg.Name, + userPath: userPath, + pkg: astPkg, + file: ast.MergePackageFiles(astPkg, 0), + doc: docPkg, + typedValue: typedValue, + constructor: constructor, + build: pkg, + fs: fs, } } @@ -390,6 +410,68 @@ func joinStrings(ss []string) string { return strings.Join(ss, ", ") } +// allDoc prints all the docs for the package. +func (pkg *Package) allDoc() { + defer pkg.flush() + if pkg.showInternals() { + pkg.packageClause(false) + } + + doc.ToText(&pkg.buf, pkg.doc.Doc, "", indent, indentedWidth) + pkg.newlines(1) + + printed := make(map[*ast.GenDecl]bool) + + hdr := "" + printHdr := func(s string) { + if hdr != s { + pkg.Printf("\n%s\n\n", s) + } + } + + // Constants. + for _, value := range pkg.doc.Consts { + // Constants and variables come in groups, and valueDoc prints + // all the items in the group. We only need to find one exported symbol. + for _, name := range value.Names { + if isExported(name) && !pkg.typedValue[value] { + printHdr("CONSTANTS") + pkg.valueDoc(value, printed) + break + } + } + } + + // Variables. + for _, value := range pkg.doc.Vars { + // Constants and variables come in groups, and valueDoc prints + // all the items in the group. We only need to find one exported symbol. + for _, name := range value.Names { + if isExported(name) && !pkg.typedValue[value] { + printHdr("VARIABLES") + pkg.valueDoc(value, printed) + break + } + } + } + + // Functions. + for _, fun := range pkg.doc.Funcs { + if isExported(fun.Name) && !pkg.constructor[fun] { + printHdr("FUNCTIONS") + pkg.emit(fun.Doc, fun.Decl) + } + } + + // Types. + for _, typ := range pkg.doc.Types { + if isExported(typ.Name) { + printHdr("TYPES") + pkg.typeDoc(typ) + } + } +} + // packageDoc prints the docs for the package (package doc plus one-liners of the rest). func (pkg *Package) packageDoc() { defer pkg.flush() @@ -426,6 +508,10 @@ func (pkg *Package) showInternals() bool { // user's argument is identical to the actual package path or // is empty, meaning it's the current directory. func (pkg *Package) packageClause(checkUserPath bool) { + if pkg.packageClausePrinted { + return + } + if checkUserPath { if pkg.userPath == "" || pkg.userPath == pkg.build.ImportPath { return @@ -463,6 +549,7 @@ func (pkg *Package) packageClause(checkUserPath bool) { if !usingModules && importPath != pkg.build.ImportPath { pkg.Printf("WARNING: package source is installed in %q\n", pkg.build.ImportPath) } + pkg.packageClausePrinted = true } // valueSummary prints a one-line summary for each set of values and constants. @@ -497,22 +584,10 @@ func (pkg *Package) valueSummary(values []*doc.Value, showGrouped bool) { // funcSummary prints a one-line summary for each function. Constructors // are printed by typeSummary, below, and so can be suppressed here. func (pkg *Package) funcSummary(funcs []*doc.Func, showConstructors bool) { - // First, identify the constructors. Don't bother figuring out if they're exported. - var isConstructor map[*doc.Func]bool - if !showConstructors { - isConstructor = make(map[*doc.Func]bool) - for _, typ := range pkg.doc.Types { - if isExported(typ.Name) { - for _, f := range typ.Funcs { - isConstructor[f] = true - } - } - } - } for _, fun := range funcs { // Exported functions only. The go/doc package does not include methods here. if isExported(fun.Name) { - if !isConstructor[fun] { + if showConstructors || !pkg.constructor[fun] { pkg.Printf("%s\n", pkg.oneLineNode(fun.Decl)) } } @@ -629,80 +704,108 @@ func (pkg *Package) symbolDoc(symbol string) bool { // So we remember which declarations we've printed to avoid duplication. printed := make(map[*ast.GenDecl]bool) for _, value := range values { - // Print each spec only if there is at least one exported symbol in it. - // (See issue 11008.) - // TODO: Should we elide unexported symbols from a single spec? - // It's an unlikely scenario, probably not worth the trouble. - // TODO: Would be nice if go/doc did this for us. - specs := make([]ast.Spec, 0, len(value.Decl.Specs)) - var typ ast.Expr - for _, spec := range value.Decl.Specs { - vspec := spec.(*ast.ValueSpec) + pkg.valueDoc(value, printed) + found = true + } + // Types. + for _, typ := range pkg.findTypes(symbol) { + pkg.typeDoc(typ) + found = true + } + if !found { + // See if there are methods. + if !pkg.printMethodDoc("", symbol) { + return false + } + } + return true +} - // The type name may carry over from a previous specification in the - // case of constants and iota. - if vspec.Type != nil { - typ = vspec.Type - } +// valueDoc prints the docs for a constant or variable. +func (pkg *Package) valueDoc(value *doc.Value, printed map[*ast.GenDecl]bool) { + if printed[value.Decl] { + return + } + // Print each spec only if there is at least one exported symbol in it. + // (See issue 11008.) + // TODO: Should we elide unexported symbols from a single spec? + // It's an unlikely scenario, probably not worth the trouble. + // TODO: Would be nice if go/doc did this for us. + specs := make([]ast.Spec, 0, len(value.Decl.Specs)) + var typ ast.Expr + for _, spec := range value.Decl.Specs { + vspec := spec.(*ast.ValueSpec) + + // The type name may carry over from a previous specification in the + // case of constants and iota. + if vspec.Type != nil { + typ = vspec.Type + } - for _, ident := range vspec.Names { - if showSrc || isExported(ident.Name) { - if vspec.Type == nil && vspec.Values == nil && typ != nil { - // This a standalone identifier, as in the case of iota usage. - // Thus, assume the type comes from the previous type. - vspec.Type = &ast.Ident{ - Name: pkg.oneLineNode(typ), - NamePos: vspec.End() - 1, - } + for _, ident := range vspec.Names { + if showSrc || isExported(ident.Name) { + if vspec.Type == nil && vspec.Values == nil && typ != nil { + // This a standalone identifier, as in the case of iota usage. + // Thus, assume the type comes from the previous type. + vspec.Type = &ast.Ident{ + Name: pkg.oneLineNode(typ), + NamePos: vspec.End() - 1, } - - specs = append(specs, vspec) - typ = nil // Only inject type on first exported identifier - break } + + specs = append(specs, vspec) + typ = nil // Only inject type on first exported identifier + break } } - if len(specs) == 0 || printed[value.Decl] { - continue - } - value.Decl.Specs = specs - if !found { - pkg.packageClause(true) - } - pkg.emit(value.Doc, value.Decl) - printed[value.Decl] = true - found = true } - // Types. - for _, typ := range pkg.findTypes(symbol) { - if !found { - pkg.packageClause(true) - } - decl := typ.Decl - spec := pkg.findTypeSpec(decl, typ.Name) - trimUnexportedElems(spec) - // If there are multiple types defined, reduce to just this one. - if len(decl.Specs) > 1 { - decl.Specs = []ast.Spec{spec} + if len(specs) == 0 { + return + } + value.Decl.Specs = specs + pkg.emit(value.Doc, value.Decl) + printed[value.Decl] = true +} + +// typeDoc prints the docs for a type, including constructors and other items +// related to it. +func (pkg *Package) typeDoc(typ *doc.Type) { + decl := typ.Decl + spec := pkg.findTypeSpec(decl, typ.Name) + trimUnexportedElems(spec) + // If there are multiple types defined, reduce to just this one. + if len(decl.Specs) > 1 { + decl.Specs = []ast.Spec{spec} + } + pkg.emit(typ.Doc, decl) + pkg.newlines(2) + // Show associated methods, constants, etc. + if showAll { + printed := make(map[*ast.GenDecl]bool) + // We can use append here to print consts, then vars. Ditto for funcs and methods. + values := typ.Consts + values = append(values, typ.Vars...) + for _, value := range values { + for _, name := range value.Names { + if isExported(name) { + pkg.valueDoc(value, printed) + break + } + } } - pkg.emit(typ.Doc, decl) - // Show associated methods, constants, etc. - if len(typ.Consts) > 0 || len(typ.Vars) > 0 || len(typ.Funcs) > 0 || len(typ.Methods) > 0 { - pkg.Printf("\n") + funcs := typ.Funcs + funcs = append(funcs, typ.Methods...) + for _, fun := range funcs { + if isExported(fun.Name) { + pkg.emit(fun.Doc, fun.Decl) + } } + } else { pkg.valueSummary(typ.Consts, true) pkg.valueSummary(typ.Vars, true) pkg.funcSummary(typ.Funcs, true) pkg.funcSummary(typ.Methods, true) - found = true - } - if !found { - // See if there are methods. - if !pkg.printMethodDoc("", symbol) { - return false - } } - return true } // trimUnexportedElems modifies spec in place to elide unexported fields from diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index 33cb6cd3b3..7866b39793 100644 --- a/src/cmd/go/alldocs.go +++ b/src/cmd/go/alldocs.go @@ -342,6 +342,8 @@ // cd go/src/encoding/json; go doc decode // // Flags: +// -all +// Show all the documentation for the package. // -c // Respect case when matching symbols. // -cmd diff --git a/src/cmd/go/internal/doc/doc.go b/src/cmd/go/internal/doc/doc.go index 262bbb3ecb..bad05ff912 100644 --- a/src/cmd/go/internal/doc/doc.go +++ b/src/cmd/go/internal/doc/doc.go @@ -106,6 +106,8 @@ Examples: cd go/src/encoding/json; go doc decode Flags: + -all + Show all the documentation for the package. -c Respect case when matching symbols. -cmd -- 2.50.0