Commit 294d16c9 authored by Rob Pike's avatar Rob Pike

cmd/doc: add a -src flag to show original source

It's long-desired but was blocked by #26835. That is now fixed, so
it's easy. When -src is off, we behave as before. But with -src
set, initialize the go/doc package to preserve the original AST and
things flow very easily.

With -src, since you're seeing inside the package source anyway it
shows unexported fields and constants: you see the original source.
But you still need -u to ask about them.

Fixes #18807

Change-Id: I473e90323b4eff0735360274dc0d2d9dba16ff8b
Reviewed-on: https://go-review.googlesource.com/c/140959Reviewed-by: default avatarAndrew Gerrand <adg@golang.org>
Run-TryBot: Andrew Gerrand <adg@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent d5e72203
...@@ -127,24 +127,24 @@ var tests = []test{ ...@@ -127,24 +127,24 @@ var tests = []test{
`type T1 = T2`, // Type alias `type T1 = T2`, // Type alias
}, },
[]string{ []string{
`const internalConstant = 2`, // No internal constants. `const internalConstant = 2`, // No internal constants.
`var internalVariable = 2`, // No internal variables. `var internalVariable = 2`, // No internal variables.
`func internalFunc(a int) bool`, // No internal functions. `func internalFunc(a int) bool`, // No internal functions.
`Comment about exported constant`, // No comment for single constant. `Comment about exported constant`, // No comment for single constant.
`Comment about exported variable`, // No comment for single variable. `Comment about exported variable`, // No comment for single variable.
`Comment about block of constants.`, // No comment for constant block. `Comment about block of constants`, // No comment for constant block.
`Comment about block of variables.`, // No comment for variable block. `Comment about block of variables`, // No comment for variable block.
`Comment before ConstOne`, // No comment for first entry in constant block. `Comment before ConstOne`, // No comment for first entry in constant block.
`Comment before VarOne`, // No comment for first entry in variable block. `Comment before VarOne`, // No comment for first entry in variable block.
`ConstTwo = 2`, // No second entry in constant block. `ConstTwo = 2`, // No second entry in constant block.
`VarTwo = 2`, // No second entry in variable block. `VarTwo = 2`, // No second entry in variable block.
`VarFive = 5`, // From block starting with unexported variable. `VarFive = 5`, // From block starting with unexported variable.
`type unexportedType`, // No unexported type. `type unexportedType`, // No unexported type.
`unexportedTypedConstant`, // No unexported typed constant. `unexportedTypedConstant`, // No unexported typed constant.
`\bField`, // No fields. `\bField`, // No fields.
`Method`, // No methods. `Method`, // No methods.
`someArgument[5-8]`, // No truncated arguments. `someArgument[5-8]`, // No truncated arguments.
`type T1 T2`, // Type alias does not display as type declaration. `type T1 T2`, // Type alias does not display as type declaration.
}, },
}, },
// Package dump -u // Package dump -u
...@@ -207,6 +207,18 @@ var tests = []test{ ...@@ -207,6 +207,18 @@ var tests = []test{
}, },
nil, nil,
}, },
// Block of constants -src.
{
"block of constants with -src",
[]string{"-src", p, `ConstTwo`},
[]string{
`Comment about block of constants`, // Top comment.
`ConstOne.*=.*1`, // Each constant seen.
`ConstTwo.*=.*2.*Comment on line with ConstTwo`,
`constThree`, // Even unexported constants.
},
nil,
},
// Block of constants with carryover type from unexported field. // Block of constants with carryover type from unexported field.
{ {
"block of constants with carryover type", "block of constants with carryover type",
...@@ -295,6 +307,17 @@ var tests = []test{ ...@@ -295,6 +307,17 @@ var tests = []test{
}, },
nil, nil,
}, },
// Function with -src.
{
"function with -src",
[]string{"-src", p, `ExportedFunc`},
[]string{
`Comment about exported function`, // Include comment.
`func ExportedFunc\(a int\) bool`,
`return true != false`, // Include body.
},
nil,
},
// Type. // Type.
{ {
...@@ -304,21 +327,44 @@ var tests = []test{ ...@@ -304,21 +327,44 @@ var tests = []test{
`Comment about exported type`, // Include comment. `Comment about exported type`, // Include comment.
`type ExportedType struct`, // Type definition. `type ExportedType struct`, // Type definition.
`Comment before exported field.*\n.*ExportedField +int` + `Comment before exported field.*\n.*ExportedField +int` +
`.*Comment on line with exported field.`, `.*Comment on line with exported field`,
`ExportedEmbeddedType.*Comment on line with exported embedded field.`, `ExportedEmbeddedType.*Comment on line with exported embedded field`,
`Has unexported fields`, `Has unexported fields`,
`func \(ExportedType\) ExportedMethod\(a int\) bool`, `func \(ExportedType\) ExportedMethod\(a int\) bool`,
`const ExportedTypedConstant ExportedType = iota`, // Must include associated constant. `const ExportedTypedConstant ExportedType = iota`, // Must include associated constant.
`func ExportedTypeConstructor\(\) \*ExportedType`, // Must include constructor. `func ExportedTypeConstructor\(\) \*ExportedType`, // Must include constructor.
`io.Reader.*Comment on line with embedded Reader.`, `io.Reader.*Comment on line with embedded Reader`,
}, },
[]string{ []string{
`unexportedField`, // No unexported field. `unexportedField`, // No unexported field.
`int.*embedded`, // No unexported embedded field. `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.
`error`, // No embedded error. `error`, // No embedded error.
},
},
// Type with -src. Will see unexported fields.
{
"type",
[]string{"-src", p, `ExportedType`},
[]string{
`Comment about exported type`, // Include comment.
`type ExportedType struct`, // Type definition.
`Comment before exported field.*\n.*ExportedField +int` +
`.*Comment on line with exported field`,
`ExportedEmbeddedType.*Comment on line with exported embedded field`,
`unexportedType.*Comment on line with unexported embedded field`,
`func \(ExportedType\) ExportedMethod\(a int\) bool`,
`const ExportedTypedConstant ExportedType = iota`, // Must include associated constant.
`func ExportedTypeConstructor\(\) \*ExportedType`, // Must include constructor.
`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.
}, },
}, },
// Type T1 dump (alias). // Type T1 dump (alias).
...@@ -341,14 +387,14 @@ var tests = []test{ ...@@ -341,14 +387,14 @@ var tests = []test{
`Comment about exported type`, // Include comment. `Comment about exported type`, // Include comment.
`type ExportedType struct`, // Type definition. `type ExportedType struct`, // Type definition.
`Comment before exported field.*\n.*ExportedField +int`, `Comment before exported field.*\n.*ExportedField +int`,
`unexportedField.*int.*Comment on line with unexported field.`, `unexportedField.*int.*Comment on line with unexported field`,
`ExportedEmbeddedType.*Comment on line with exported embedded field.`, `ExportedEmbeddedType.*Comment on line with exported embedded field`,
`\*ExportedEmbeddedType.*Comment on line with exported embedded \*field.`, `\*ExportedEmbeddedType.*Comment on line with exported embedded \*field`,
`\*qualified.ExportedEmbeddedType.*Comment on line with exported embedded \*selector.field.`, `\*qualified.ExportedEmbeddedType.*Comment on line with exported embedded \*selector.field`,
`unexportedType.*Comment on line with unexported embedded field.`, `unexportedType.*Comment on line with unexported embedded field`,
`\*unexportedType.*Comment on line with unexported embedded \*field.`, `\*unexportedType.*Comment on line with unexported embedded \*field`,
`io.Reader.*Comment on line with embedded Reader.`, `io.Reader.*Comment on line with embedded Reader`,
`error.*Comment on line with embedded error.`, `error.*Comment on line with embedded error`,
`func \(ExportedType\) unexportedMethod\(a int\) bool`, `func \(ExportedType\) unexportedMethod\(a int\) bool`,
`unexportedTypedConstant`, `unexportedTypedConstant`,
}, },
...@@ -380,8 +426,8 @@ var tests = []test{ ...@@ -380,8 +426,8 @@ var tests = []test{
`type ExportedInterface interface`, // Interface definition. `type ExportedInterface interface`, // Interface definition.
`Comment before exported method.*\n.*ExportedMethod\(\)` + `Comment before exported method.*\n.*ExportedMethod\(\)` +
`.*Comment on line with exported method`, `.*Comment on line with exported method`,
`io.Reader.*Comment on line with embedded Reader.`, `io.Reader.*Comment on line with embedded Reader`,
`error.*Comment on line with embedded error.`, `error.*Comment on line with embedded error`,
`Has unexported methods`, `Has unexported methods`,
}, },
[]string{ []string{
...@@ -400,9 +446,9 @@ var tests = []test{ ...@@ -400,9 +446,9 @@ var tests = []test{
`type ExportedInterface interface`, // Interface definition. `type ExportedInterface interface`, // Interface definition.
`Comment before exported method.*\n.*ExportedMethod\(\)` + `Comment before exported method.*\n.*ExportedMethod\(\)` +
`.*Comment on line with exported method`, `.*Comment on line with exported method`,
`unexportedMethod\(\).*Comment on line with unexported method.`, `unexportedMethod\(\).*Comment on line with unexported method`,
`io.Reader.*Comment on line with embedded Reader.`, `io.Reader.*Comment on line with embedded Reader`,
`error.*Comment on line with embedded error.`, `error.*Comment on line with embedded error`,
}, },
[]string{ []string{
`Has unexported methods`, `Has unexported methods`,
...@@ -418,7 +464,7 @@ var tests = []test{ ...@@ -418,7 +464,7 @@ var tests = []test{
`.*Comment on line with exported method`, `.*Comment on line with exported method`,
}, },
[]string{ []string{
`Comment about exported interface.`, `Comment about exported interface`,
}, },
}, },
...@@ -428,7 +474,7 @@ var tests = []test{ ...@@ -428,7 +474,7 @@ var tests = []test{
[]string{p, `ExportedType.ExportedMethod`}, []string{p, `ExportedType.ExportedMethod`},
[]string{ []string{
`func \(ExportedType\) ExportedMethod\(a int\) bool`, `func \(ExportedType\) ExportedMethod\(a int\) bool`,
`Comment about exported method.`, `Comment about exported method`,
}, },
nil, nil,
}, },
...@@ -438,7 +484,18 @@ var tests = []test{ ...@@ -438,7 +484,18 @@ var tests = []test{
[]string{"-u", p, `ExportedType.unexportedMethod`}, []string{"-u", p, `ExportedType.unexportedMethod`},
[]string{ []string{
`func \(ExportedType\) unexportedMethod\(a int\) bool`, `func \(ExportedType\) unexportedMethod\(a int\) bool`,
`Comment about unexported method.`, `Comment about unexported method`,
},
nil,
},
// Method with -src.
{
"method with -src",
[]string{"-src", p, `ExportedType.ExportedMethod`},
[]string{
`func \(ExportedType\) ExportedMethod\(a int\) bool`,
`Comment about exported method`,
`return true != true`,
}, },
nil, nil,
}, },
...@@ -450,8 +507,8 @@ var tests = []test{ ...@@ -450,8 +507,8 @@ var tests = []test{
[]string{ []string{
`type ExportedType struct`, `type ExportedType struct`,
`ExportedField int`, `ExportedField int`,
`Comment before exported field.`, `Comment before exported field`,
`Comment on line with exported field.`, `Comment on line with exported field`,
`other fields elided`, `other fields elided`,
}, },
nil, nil,
...@@ -463,7 +520,7 @@ var tests = []test{ ...@@ -463,7 +520,7 @@ var tests = []test{
[]string{"-u", p, `ExportedType.unexportedField`}, []string{"-u", p, `ExportedType.unexportedField`},
[]string{ []string{
`unexportedField int`, `unexportedField int`,
`Comment on line with unexported field.`, `Comment on line with unexported field`,
}, },
nil, nil,
}, },
......
...@@ -28,6 +28,9 @@ ...@@ -28,6 +28,9 @@
// For commands, unless the -cmd flag is present "go doc command" // For commands, unless the -cmd flag is present "go doc command"
// shows only the package-level docs for the package. // shows only the package-level docs for the package.
// //
// The -src flag causes doc to print the full source code for the symbol, such
// as the body of a struct, function or method.
//
// For complete documentation, run "go help doc". // For complete documentation, run "go help doc".
package main package main
...@@ -50,6 +53,7 @@ var ( ...@@ -50,6 +53,7 @@ var (
unexported bool // -u flag unexported bool // -u flag
matchCase bool // -c flag matchCase bool // -c flag
showCmd bool // -cmd flag showCmd bool // -cmd flag
showSrc bool // -src flag
) )
// usage is a replacement usage function for the flags package. // usage is a replacement usage function for the flags package.
...@@ -85,6 +89,7 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) { ...@@ -85,6 +89,7 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) {
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(&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.Parse(args) flagSet.Parse(args)
var paths []string var paths []string
var symbol, method string var symbol, method string
......
...@@ -137,7 +137,11 @@ func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Packag ...@@ -137,7 +137,11 @@ func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Packag
// from finding the symbol. Work around this for now, but we // from finding the symbol. Work around this for now, but we
// should fix it in go/doc. // should fix it in go/doc.
// A similar story applies to factory functions. // A similar story applies to factory functions.
docPkg := doc.New(astPkg, pkg.ImportPath, doc.AllDecls) mode := doc.AllDecls
if showSrc {
mode |= doc.PreserveAST // See comment for Package.emit.
}
docPkg := doc.New(astPkg, pkg.ImportPath, mode)
for _, typ := range docPkg.Types { for _, typ := range docPkg.Types {
docPkg.Consts = append(docPkg.Consts, typ.Consts...) docPkg.Consts = append(docPkg.Consts, typ.Consts...)
docPkg.Vars = append(docPkg.Vars, typ.Vars...) docPkg.Vars = append(docPkg.Vars, typ.Vars...)
...@@ -177,14 +181,16 @@ func (pkg *Package) newlines(n int) { ...@@ -177,14 +181,16 @@ func (pkg *Package) newlines(n int) {
} }
} }
// emit prints the node. // emit prints the node. If showSrc is true, it ignores the provided comment,
// assuming the comment is in the node itself. Otherwise, the go/doc package
// clears the stuff we don't want to print anyway. It's a bit of a magic trick.
func (pkg *Package) emit(comment string, node ast.Node) { func (pkg *Package) emit(comment string, node ast.Node) {
if node != nil { if node != nil {
err := format.Node(&pkg.buf, pkg.fs, node) err := format.Node(&pkg.buf, pkg.fs, node)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if comment != "" { if comment != "" && !showSrc {
pkg.newlines(1) pkg.newlines(1)
doc.ToText(&pkg.buf, comment, " ", indent, indentedWidth) doc.ToText(&pkg.buf, comment, " ", indent, indentedWidth)
pkg.newlines(2) // Blank line after comment to separate from next item. pkg.newlines(2) // Blank line after comment to separate from next item.
...@@ -611,7 +617,6 @@ func (pkg *Package) symbolDoc(symbol string) bool { ...@@ -611,7 +617,6 @@ func (pkg *Package) symbolDoc(symbol string) bool {
} }
// Symbol is a function. // Symbol is a function.
decl := fun.Decl decl := fun.Decl
decl.Body = nil
pkg.emit(fun.Doc, decl) pkg.emit(fun.Doc, decl)
found = true found = true
} }
...@@ -641,7 +646,7 @@ func (pkg *Package) symbolDoc(symbol string) bool { ...@@ -641,7 +646,7 @@ func (pkg *Package) symbolDoc(symbol string) bool {
} }
for _, ident := range vspec.Names { for _, ident := range vspec.Names {
if isExported(ident.Name) { if showSrc || isExported(ident.Name) {
if vspec.Type == nil && vspec.Values == nil && typ != nil { if vspec.Type == nil && vspec.Values == nil && typ != nil {
// This a standalone identifier, as in the case of iota usage. // This a standalone identifier, as in the case of iota usage.
// Thus, assume the type comes from the previous type. // Thus, assume the type comes from the previous type.
...@@ -701,9 +706,10 @@ func (pkg *Package) symbolDoc(symbol string) bool { ...@@ -701,9 +706,10 @@ func (pkg *Package) symbolDoc(symbol string) bool {
} }
// trimUnexportedElems modifies spec in place to elide unexported fields from // trimUnexportedElems modifies spec in place to elide unexported fields from
// structs and methods from interfaces (unless the unexported flag is set). // structs and methods from interfaces (unless the unexported flag is set or we
// are asked to show the original source).
func trimUnexportedElems(spec *ast.TypeSpec) { func trimUnexportedElems(spec *ast.TypeSpec) {
if unexported { if unexported || showSrc {
return return
} }
switch typ := spec.Type.(type) { switch typ := spec.Type.(type) {
...@@ -808,7 +814,6 @@ func (pkg *Package) printMethodDoc(symbol, method string) bool { ...@@ -808,7 +814,6 @@ func (pkg *Package) printMethodDoc(symbol, method string) bool {
for _, meth := range typ.Methods { for _, meth := range typ.Methods {
if match(method, meth.Name) { if match(method, meth.Name) {
decl := meth.Decl decl := meth.Decl
decl.Body = nil
pkg.emit(meth.Doc, decl) pkg.emit(meth.Doc, decl)
found = true found = true
} }
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
// Package comment. // Package comment.
package pkg package pkg
import "io"
// Constants // Constants
// Comment about exported constant. // Comment about exported constant.
...@@ -52,7 +54,9 @@ var ( ...@@ -52,7 +54,9 @@ var (
) )
// Comment about exported function. // Comment about exported function.
func ExportedFunc(a int) bool func ExportedFunc(a int) bool {
return true != false
}
// Comment about internal function. // Comment about internal function.
func internalFunc(a int) bool func internalFunc(a int) bool
...@@ -73,7 +77,7 @@ type ExportedType struct { ...@@ -73,7 +77,7 @@ type ExportedType struct {
// Comment about exported method. // Comment about exported method.
func (ExportedType) ExportedMethod(a int) bool { func (ExportedType) ExportedMethod(a int) bool {
return true return true != true
} }
// Comment about unexported method. // Comment about unexported method.
......
...@@ -348,6 +348,13 @@ ...@@ -348,6 +348,13 @@
// Treat a command (package main) like a regular package. // Treat a command (package main) like a regular package.
// Otherwise package main's exported symbols are hidden // Otherwise package main's exported symbols are hidden
// when showing the package's top-level documentation. // when showing the package's top-level documentation.
// -src
// Show the full source code for the symbol. This will
// display the full Go source of its declaration and
// definition, such as a function definition (including
// the body), type declaration or enclosing const
// block. The output may therefore include unexported
// details.
// -u // -u
// Show documentation for unexported as well as exported // Show documentation for unexported as well as exported
// symbols, methods, and fields. // symbols, methods, and fields.
......
...@@ -112,6 +112,13 @@ Flags: ...@@ -112,6 +112,13 @@ Flags:
Treat a command (package main) like a regular package. Treat a command (package main) like a regular package.
Otherwise package main's exported symbols are hidden Otherwise package main's exported symbols are hidden
when showing the package's top-level documentation. when showing the package's top-level documentation.
-src
Show the full source code for the symbol. This will
display the full Go source of its declaration and
definition, such as a function definition (including
the body), type declaration or enclosing const
block. The output may therefore include unexported
details.
-u -u
Show documentation for unexported as well as exported Show documentation for unexported as well as exported
symbols, methods, and fields. symbols, methods, and fields.
......
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