Commit 101a677e authored by Rob Pike's avatar Rob Pike

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 <r@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarRuss Cox <rsc@golang.org>
parent ee769922
...@@ -147,6 +147,69 @@ var tests = []test{ ...@@ -147,6 +147,69 @@ var tests = []test{
`type T1 T2`, // Type alias does not display as type declaration. `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 // Package dump -u
{ {
"full package with u", "full package with u",
...@@ -164,6 +227,58 @@ var tests = []test{ ...@@ -164,6 +227,58 @@ var tests = []test{
`MultiLine(String|Method|Field)`, // No data from multi line portions. `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. // Single constant.
{ {
...@@ -361,7 +476,6 @@ var tests = []test{ ...@@ -361,7 +476,6 @@ var tests = []test{
`io.Reader.*Comment on line with embedded Reader`, `io.Reader.*Comment on line with embedded Reader`,
}, },
[]string{ []string{
`int.*embedded`, // No unexported embedded field.
`Comment about exported method`, // No comment about exported method. `Comment about exported method`, // No comment about exported method.
`unexportedMethod`, // No unexported method. `unexportedMethod`, // No unexported method.
`unexportedTypedConstant`, // No unexported constant. `unexportedTypedConstant`, // No unexported constant.
......
...@@ -31,6 +31,9 @@ ...@@ -31,6 +31,9 @@
// The -src flag causes doc to print the full source code for the symbol, such // The -src flag causes doc to print the full source code for the symbol, such
// as the body of a struct, function or method. // 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". // For complete documentation, run "go help doc".
package main package main
...@@ -52,6 +55,7 @@ import ( ...@@ -52,6 +55,7 @@ import (
var ( var (
unexported bool // -u flag unexported bool // -u flag
matchCase bool // -c flag matchCase bool // -c flag
showAll bool // -all flag
showCmd bool // -cmd flag showCmd bool // -cmd flag
showSrc bool // -src flag showSrc bool // -src flag
) )
...@@ -88,6 +92,7 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) { ...@@ -88,6 +92,7 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) {
matchCase = false matchCase = false
flagSet.BoolVar(&unexported, "u", false, "show unexported symbols as well as exported") 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(&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(&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.BoolVar(&showSrc, "src", false, "show source code for symbol")
flagSet.Parse(args) flagSet.Parse(args)
...@@ -127,6 +132,15 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) { ...@@ -127,6 +132,15 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) {
unexported = true 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 { switch {
case symbol == "": case symbol == "":
pkg.packageDoc() // The package exists, so we got some output. pkg.packageDoc() // The package exists, so we got some output.
......
...@@ -36,6 +36,9 @@ type Package struct { ...@@ -36,6 +36,9 @@ type Package struct {
file *ast.File // Merged from all files in the package file *ast.File // Merged from all files in the package
doc *doc.Package doc *doc.Package
build *build.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. fs *token.FileSet // Needed for printing.
buf bytes.Buffer buf bytes.Buffer
} }
...@@ -142,10 +145,25 @@ func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Packag ...@@ -142,10 +145,25 @@ func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Packag
mode |= doc.PreserveAST // See comment for Package.emit. mode |= doc.PreserveAST // See comment for Package.emit.
} }
docPkg := doc.New(astPkg, pkg.ImportPath, mode) docPkg := doc.New(astPkg, pkg.ImportPath, mode)
typedValue := make(map[*doc.Value]bool)
constructor := make(map[*doc.Func]bool)
for _, typ := range docPkg.Types { for _, typ := range docPkg.Types {
docPkg.Consts = append(docPkg.Consts, typ.Consts...) docPkg.Consts = append(docPkg.Consts, typ.Consts...)
for _, value := range typ.Consts {
typedValue[value] = true
}
docPkg.Vars = append(docPkg.Vars, typ.Vars...) docPkg.Vars = append(docPkg.Vars, typ.Vars...)
for _, value := range typ.Vars {
typedValue[value] = true
}
docPkg.Funcs = append(docPkg.Funcs, typ.Funcs...) 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{ return &Package{
...@@ -155,6 +173,8 @@ func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Packag ...@@ -155,6 +173,8 @@ func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Packag
pkg: astPkg, pkg: astPkg,
file: ast.MergePackageFiles(astPkg, 0), file: ast.MergePackageFiles(astPkg, 0),
doc: docPkg, doc: docPkg,
typedValue: typedValue,
constructor: constructor,
build: pkg, build: pkg,
fs: fs, fs: fs,
} }
...@@ -390,6 +410,68 @@ func joinStrings(ss []string) string { ...@@ -390,6 +410,68 @@ func joinStrings(ss []string) string {
return strings.Join(ss, ", ") 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). // packageDoc prints the docs for the package (package doc plus one-liners of the rest).
func (pkg *Package) packageDoc() { func (pkg *Package) packageDoc() {
defer pkg.flush() defer pkg.flush()
...@@ -426,6 +508,10 @@ func (pkg *Package) showInternals() bool { ...@@ -426,6 +508,10 @@ func (pkg *Package) showInternals() bool {
// user's argument is identical to the actual package path or // user's argument is identical to the actual package path or
// is empty, meaning it's the current directory. // is empty, meaning it's the current directory.
func (pkg *Package) packageClause(checkUserPath bool) { func (pkg *Package) packageClause(checkUserPath bool) {
if pkg.packageClausePrinted {
return
}
if checkUserPath { if checkUserPath {
if pkg.userPath == "" || pkg.userPath == pkg.build.ImportPath { if pkg.userPath == "" || pkg.userPath == pkg.build.ImportPath {
return return
...@@ -463,6 +549,7 @@ func (pkg *Package) packageClause(checkUserPath bool) { ...@@ -463,6 +549,7 @@ func (pkg *Package) packageClause(checkUserPath bool) {
if !usingModules && importPath != pkg.build.ImportPath { if !usingModules && importPath != pkg.build.ImportPath {
pkg.Printf("WARNING: package source is installed in %q\n", 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. // 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) { ...@@ -497,22 +584,10 @@ func (pkg *Package) valueSummary(values []*doc.Value, showGrouped bool) {
// funcSummary prints a one-line summary for each function. Constructors // funcSummary prints a one-line summary for each function. Constructors
// are printed by typeSummary, below, and so can be suppressed here. // are printed by typeSummary, below, and so can be suppressed here.
func (pkg *Package) funcSummary(funcs []*doc.Func, showConstructors bool) { 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 { for _, fun := range funcs {
// Exported functions only. The go/doc package does not include methods here. // Exported functions only. The go/doc package does not include methods here.
if isExported(fun.Name) { if isExported(fun.Name) {
if !isConstructor[fun] { if showConstructors || !pkg.constructor[fun] {
pkg.Printf("%s\n", pkg.oneLineNode(fun.Decl)) pkg.Printf("%s\n", pkg.oneLineNode(fun.Decl))
} }
} }
...@@ -629,6 +704,28 @@ func (pkg *Package) symbolDoc(symbol string) bool { ...@@ -629,6 +704,28 @@ func (pkg *Package) symbolDoc(symbol string) bool {
// So we remember which declarations we've printed to avoid duplication. // So we remember which declarations we've printed to avoid duplication.
printed := make(map[*ast.GenDecl]bool) printed := make(map[*ast.GenDecl]bool)
for _, value := range values { for _, value := range values {
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
}
// 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. // Print each spec only if there is at least one exported symbol in it.
// (See issue 11008.) // (See issue 11008.)
// TODO: Should we elide unexported symbols from a single spec? // TODO: Should we elide unexported symbols from a single spec?
...@@ -662,22 +759,17 @@ func (pkg *Package) symbolDoc(symbol string) bool { ...@@ -662,22 +759,17 @@ func (pkg *Package) symbolDoc(symbol string) bool {
} }
} }
} }
if len(specs) == 0 || printed[value.Decl] { if len(specs) == 0 {
continue return
} }
value.Decl.Specs = specs value.Decl.Specs = specs
if !found {
pkg.packageClause(true)
}
pkg.emit(value.Doc, value.Decl) pkg.emit(value.Doc, value.Decl)
printed[value.Decl] = true printed[value.Decl] = true
found = true }
}
// Types. // typeDoc prints the docs for a type, including constructors and other items
for _, typ := range pkg.findTypes(symbol) { // related to it.
if !found { func (pkg *Package) typeDoc(typ *doc.Type) {
pkg.packageClause(true)
}
decl := typ.Decl decl := typ.Decl
spec := pkg.findTypeSpec(decl, typ.Name) spec := pkg.findTypeSpec(decl, typ.Name)
trimUnexportedElems(spec) trimUnexportedElems(spec)
...@@ -686,23 +778,34 @@ func (pkg *Package) symbolDoc(symbol string) bool { ...@@ -686,23 +778,34 @@ func (pkg *Package) symbolDoc(symbol string) bool {
decl.Specs = []ast.Spec{spec} decl.Specs = []ast.Spec{spec}
} }
pkg.emit(typ.Doc, decl) pkg.emit(typ.Doc, decl)
pkg.newlines(2)
// Show associated methods, constants, etc. // Show associated methods, constants, etc.
if len(typ.Consts) > 0 || len(typ.Vars) > 0 || len(typ.Funcs) > 0 || len(typ.Methods) > 0 { if showAll {
pkg.Printf("\n") 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
} }
}
}
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.Consts, true)
pkg.valueSummary(typ.Vars, true) pkg.valueSummary(typ.Vars, true)
pkg.funcSummary(typ.Funcs, true) pkg.funcSummary(typ.Funcs, true)
pkg.funcSummary(typ.Methods, 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 // trimUnexportedElems modifies spec in place to elide unexported fields from
......
...@@ -342,6 +342,8 @@ ...@@ -342,6 +342,8 @@
// cd go/src/encoding/json; go doc decode // cd go/src/encoding/json; go doc decode
// //
// Flags: // Flags:
// -all
// Show all the documentation for the package.
// -c // -c
// Respect case when matching symbols. // Respect case when matching symbols.
// -cmd // -cmd
......
...@@ -106,6 +106,8 @@ Examples: ...@@ -106,6 +106,8 @@ Examples:
cd go/src/encoding/json; go doc decode cd go/src/encoding/json; go doc decode
Flags: Flags:
-all
Show all the documentation for the package.
-c -c
Respect case when matching symbols. Respect case when matching symbols.
-cmd -cmd
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment