From 294d16c9c58187492baf1fd8bac034b5fecbf0ba Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Wed, 10 Oct 2018 11:50:49 +1100 Subject: [PATCH] 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/140959 Reviewed-by: Andrew Gerrand Run-TryBot: Andrew Gerrand TryBot-Result: Gobot Gobot --- src/cmd/doc/doc_test.go | 149 +++++++++++++++++++++++---------- src/cmd/doc/main.go | 5 ++ src/cmd/doc/pkg.go | 21 +++-- src/cmd/doc/testdata/pkg.go | 8 +- src/cmd/go/alldocs.go | 7 ++ src/cmd/go/internal/doc/doc.go | 7 ++ 6 files changed, 141 insertions(+), 56 deletions(-) diff --git a/src/cmd/doc/doc_test.go b/src/cmd/doc/doc_test.go index 6010f04b560..64b1fb596b4 100644 --- a/src/cmd/doc/doc_test.go +++ b/src/cmd/doc/doc_test.go @@ -127,24 +127,24 @@ var tests = []test{ `type T1 = T2`, // Type alias }, []string{ - `const internalConstant = 2`, // No internal constants. - `var internalVariable = 2`, // No internal variables. - `func internalFunc(a int) bool`, // No internal functions. - `Comment about exported constant`, // No comment for single constant. - `Comment about exported variable`, // No comment for single variable. - `Comment about block of constants.`, // No comment for constant block. - `Comment about block of variables.`, // No comment for variable block. - `Comment before ConstOne`, // No comment for first entry in constant block. - `Comment before VarOne`, // No comment for first entry in variable block. - `ConstTwo = 2`, // No second entry in constant block. - `VarTwo = 2`, // No second entry in variable block. - `VarFive = 5`, // From block starting with unexported variable. - `type unexportedType`, // No unexported type. - `unexportedTypedConstant`, // No unexported typed constant. - `\bField`, // No fields. - `Method`, // No methods. - `someArgument[5-8]`, // No truncated arguments. - `type T1 T2`, // Type alias does not display as type declaration. + `const internalConstant = 2`, // No internal constants. + `var internalVariable = 2`, // No internal variables. + `func internalFunc(a int) bool`, // No internal functions. + `Comment about exported constant`, // No comment for single constant. + `Comment about exported variable`, // No comment for single variable. + `Comment about block of constants`, // No comment for constant block. + `Comment about block of variables`, // No comment for variable block. + `Comment before ConstOne`, // No comment for first entry in constant block. + `Comment before VarOne`, // No comment for first entry in variable block. + `ConstTwo = 2`, // No second entry in constant block. + `VarTwo = 2`, // No second entry in variable block. + `VarFive = 5`, // From block starting with unexported variable. + `type unexportedType`, // No unexported type. + `unexportedTypedConstant`, // No unexported typed constant. + `\bField`, // No fields. + `Method`, // No methods. + `someArgument[5-8]`, // No truncated arguments. + `type T1 T2`, // Type alias does not display as type declaration. }, }, // Package dump -u @@ -207,6 +207,18 @@ var tests = []test{ }, 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", @@ -295,6 +307,17 @@ var tests = []test{ }, 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. { @@ -304,21 +327,44 @@ var tests = []test{ `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.`, + `.*Comment on line with exported field`, + `ExportedEmbeddedType.*Comment on line with exported embedded field`, `Has unexported fields`, `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.`, + `io.Reader.*Comment on line with embedded Reader`, }, []string{ - `unexportedField`, // No unexported field. - `int.*embedded`, // No unexported embedded field. - `Comment about exported method.`, // No comment about exported method. - `unexportedMethod`, // No unexported method. - `unexportedTypedConstant`, // No unexported constant. - `error`, // No embedded error. + `unexportedField`, // No unexported field. + `int.*embedded`, // No unexported embedded field. + `Comment about exported method`, // No comment about exported method. + `unexportedMethod`, // No unexported method. + `unexportedTypedConstant`, // No unexported constant. + `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). @@ -341,14 +387,14 @@ var tests = []test{ `Comment about exported type`, // Include comment. `type ExportedType struct`, // Type definition. `Comment before exported field.*\n.*ExportedField +int`, - `unexportedField.*int.*Comment on line with unexported 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.`, - `unexportedType.*Comment on line with unexported embedded field.`, - `\*unexportedType.*Comment on line with unexported embedded \*field.`, - `io.Reader.*Comment on line with embedded Reader.`, - `error.*Comment on line with embedded error.`, + `unexportedField.*int.*Comment on line with unexported 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`, + `unexportedType.*Comment on line with unexported embedded field`, + `\*unexportedType.*Comment on line with unexported embedded \*field`, + `io.Reader.*Comment on line with embedded Reader`, + `error.*Comment on line with embedded error`, `func \(ExportedType\) unexportedMethod\(a int\) bool`, `unexportedTypedConstant`, }, @@ -380,8 +426,8 @@ var tests = []test{ `type ExportedInterface interface`, // Interface definition. `Comment before exported method.*\n.*ExportedMethod\(\)` + `.*Comment on line with exported method`, - `io.Reader.*Comment on line with embedded Reader.`, - `error.*Comment on line with embedded error.`, + `io.Reader.*Comment on line with embedded Reader`, + `error.*Comment on line with embedded error`, `Has unexported methods`, }, []string{ @@ -400,9 +446,9 @@ var tests = []test{ `type ExportedInterface interface`, // Interface definition. `Comment before exported method.*\n.*ExportedMethod\(\)` + `.*Comment on line with exported method`, - `unexportedMethod\(\).*Comment on line with unexported method.`, - `io.Reader.*Comment on line with embedded Reader.`, - `error.*Comment on line with embedded error.`, + `unexportedMethod\(\).*Comment on line with unexported method`, + `io.Reader.*Comment on line with embedded Reader`, + `error.*Comment on line with embedded error`, }, []string{ `Has unexported methods`, @@ -418,7 +464,7 @@ var tests = []test{ `.*Comment on line with exported method`, }, []string{ - `Comment about exported interface.`, + `Comment about exported interface`, }, }, @@ -428,7 +474,7 @@ var tests = []test{ []string{p, `ExportedType.ExportedMethod`}, []string{ `func \(ExportedType\) ExportedMethod\(a int\) bool`, - `Comment about exported method.`, + `Comment about exported method`, }, nil, }, @@ -438,7 +484,18 @@ var tests = []test{ []string{"-u", p, `ExportedType.unexportedMethod`}, []string{ `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, }, @@ -450,8 +507,8 @@ var tests = []test{ []string{ `type ExportedType struct`, `ExportedField int`, - `Comment before exported field.`, - `Comment on line with exported field.`, + `Comment before exported field`, + `Comment on line with exported field`, `other fields elided`, }, nil, @@ -463,7 +520,7 @@ var tests = []test{ []string{"-u", p, `ExportedType.unexportedField`}, []string{ `unexportedField int`, - `Comment on line with unexported field.`, + `Comment on line with unexported field`, }, nil, }, diff --git a/src/cmd/doc/main.go b/src/cmd/doc/main.go index 982c8e054ac..a3e09d3f871 100644 --- a/src/cmd/doc/main.go +++ b/src/cmd/doc/main.go @@ -28,6 +28,9 @@ // For commands, unless the -cmd flag is present "go doc command" // 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". package main @@ -50,6 +53,7 @@ var ( unexported bool // -u flag matchCase bool // -c flag showCmd bool // -cmd flag + showSrc bool // -src flag ) // 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) { 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(&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) var paths []string var symbol, method string diff --git a/src/cmd/doc/pkg.go b/src/cmd/doc/pkg.go index 14e41b9106c..154fb7b45fc 100644 --- a/src/cmd/doc/pkg.go +++ b/src/cmd/doc/pkg.go @@ -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 // should fix it in go/doc. // 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 { docPkg.Consts = append(docPkg.Consts, typ.Consts...) docPkg.Vars = append(docPkg.Vars, typ.Vars...) @@ -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) { if node != nil { err := format.Node(&pkg.buf, pkg.fs, node) if err != nil { log.Fatal(err) } - if comment != "" { + if comment != "" && !showSrc { pkg.newlines(1) doc.ToText(&pkg.buf, comment, " ", indent, indentedWidth) pkg.newlines(2) // Blank line after comment to separate from next item. @@ -611,7 +617,6 @@ func (pkg *Package) symbolDoc(symbol string) bool { } // Symbol is a function. decl := fun.Decl - decl.Body = nil pkg.emit(fun.Doc, decl) found = true } @@ -641,7 +646,7 @@ func (pkg *Package) symbolDoc(symbol string) bool { } for _, ident := range vspec.Names { - if isExported(ident.Name) { + 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. @@ -701,9 +706,10 @@ func (pkg *Package) symbolDoc(symbol string) bool { } // 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) { - if unexported { + if unexported || showSrc { return } switch typ := spec.Type.(type) { @@ -808,7 +814,6 @@ func (pkg *Package) printMethodDoc(symbol, method string) bool { for _, meth := range typ.Methods { if match(method, meth.Name) { decl := meth.Decl - decl.Body = nil pkg.emit(meth.Doc, decl) found = true } diff --git a/src/cmd/doc/testdata/pkg.go b/src/cmd/doc/testdata/pkg.go index bc069939f81..50105b5fccc 100644 --- a/src/cmd/doc/testdata/pkg.go +++ b/src/cmd/doc/testdata/pkg.go @@ -5,6 +5,8 @@ // Package comment. package pkg +import "io" + // Constants // Comment about exported constant. @@ -52,7 +54,9 @@ var ( ) // Comment about exported function. -func ExportedFunc(a int) bool +func ExportedFunc(a int) bool { + return true != false +} // Comment about internal function. func internalFunc(a int) bool @@ -73,7 +77,7 @@ type ExportedType struct { // Comment about exported method. func (ExportedType) ExportedMethod(a int) bool { - return true + return true != true } // Comment about unexported method. diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index 9528ca29844..f54f000b077 100644 --- a/src/cmd/go/alldocs.go +++ b/src/cmd/go/alldocs.go @@ -348,6 +348,13 @@ // Treat a command (package main) like a regular package. // Otherwise package main's exported symbols are hidden // 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 // Show documentation for unexported as well as exported // symbols, methods, and fields. diff --git a/src/cmd/go/internal/doc/doc.go b/src/cmd/go/internal/doc/doc.go index 4e7dca082d7..262bbb3ecb6 100644 --- a/src/cmd/go/internal/doc/doc.go +++ b/src/cmd/go/internal/doc/doc.go @@ -112,6 +112,13 @@ Flags: Treat a command (package main) like a regular package. Otherwise package main's exported symbols are hidden 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 Show documentation for unexported as well as exported symbols, methods, and fields.