aboutsummaryrefslogtreecommitdiff
path: root/src/cmd
diff options
context:
space:
mode:
authorNick White <git@njw.name>2026-02-10 08:26:25 +0000
committerMichael Matloob <matloob@golang.org>2026-03-27 07:58:59 -0700
commit7f2e2fd71f0b25fde6031b8317f525f1a2739e9b (patch)
tree52c3edfccbc6400fd26f7447823ea7163269e7d1 /src/cmd
parent31a839621976f968a997fd8578b6c5fd074bbc0c (diff)
downloadgo-7f2e2fd71f0b25fde6031b8317f525f1a2739e9b.tar.xz
cmd/go: add example support to go doc output
The go doc command now includes a -ex flag to list executable examples. It will also print the code and expected output of an example when passed its name. Fixes #26715 Change-Id: I34b09403cc3cb45655939bd4fe27accec0e141f5 Reviewed-on: https://go-review.googlesource.com/c/go/+/445116 Reviewed-by: Rob Pike <r@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Matloob <matloob@golang.org> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Reviewed-by: Michael Matloob <matloob@google.com>
Diffstat (limited to 'src/cmd')
-rw-r--r--src/cmd/go/alldocs.go2
-rw-r--r--src/cmd/go/internal/doc/doc.go4
-rw-r--r--src/cmd/go/internal/doc/doc_test.go137
-rw-r--r--src/cmd/go/internal/doc/pkg.go124
-rw-r--r--src/cmd/go/internal/doc/testdata/pkg_test.go47
-rw-r--r--src/cmd/go/internal/doc/testdata/play_test.go13
6 files changed, 312 insertions, 15 deletions
diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go
index 4be78b33bb..e394ef836a 100644
--- a/src/cmd/go/alldocs.go
+++ b/src/cmd/go/alldocs.go
@@ -452,6 +452,8 @@
// 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.
+// -ex
+// Include executable examples.
// -http
// Serve HTML docs over HTTP.
// -short
diff --git a/src/cmd/go/internal/doc/doc.go b/src/cmd/go/internal/doc/doc.go
index a68c8cb3a3..fd6d663375 100644
--- a/src/cmd/go/internal/doc/doc.go
+++ b/src/cmd/go/internal/doc/doc.go
@@ -129,6 +129,8 @@ 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.
+ -ex
+ Include executable examples.
-http
Serve HTML docs over HTTP.
-short
@@ -163,6 +165,7 @@ var (
chdir string // -C flag
showAll bool // -all flag
showCmd bool // -cmd flag
+ showEx bool // -ex flag
showSrc bool // -src flag
short bool // -short flag
serveHTTP bool // -http flag
@@ -191,6 +194,7 @@ func do(ctx context.Context, writer io.Writer, flagSet *flag.FlagSet, args []str
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(&showEx, "ex", false, "show executable examples for symbol or 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.BoolVar(&short, "short", false, "one-line representation for each symbol")
diff --git a/src/cmd/go/internal/doc/doc_test.go b/src/cmd/go/internal/doc/doc_test.go
index bb415f6991..3eeaffc25b 100644
--- a/src/cmd/go/internal/doc/doc_test.go
+++ b/src/cmd/go/internal/doc/doc_test.go
@@ -750,6 +750,143 @@ var tests = []test{
[]string{`other fields elided`},
},
+ // Package with -ex.
+ {
+ "package with -ex",
+ []string{`-ex`, p},
+ []string{
+ `func ExampleExportedFunc\(\)`,
+ `func ExampleExportedType\(\)`,
+ `func Example\(\)`,
+ `func Example_multiline\(\)`,
+ `func Example_playable\(\)`,
+ },
+ nil,
+ },
+
+ // Type with -ex.
+ {
+ "type with -ex",
+ []string{`-ex`, p, `ExportedType`},
+ []string{
+ `func ExampleExportedType\(\)`,
+ `func ExampleExportedType_ExportedMethod\(\)`,
+ },
+ nil,
+ },
+
+ // Function with -ex.
+ {
+ "function with -ex",
+ []string{`-ex`, p, `ExportedFunc`},
+ []string{
+ `func ExampleExportedFunc\(\)`,
+ `Function example.`,
+ `func ExampleExportedFunc_two\(\)`,
+ `Function example two.`,
+ },
+ nil,
+ },
+
+ // Method with -ex.
+ {
+ "method with -ex",
+ []string{`-ex`, p, `ExportedType.ExportedMethod`},
+ []string{
+ `func ExampleExportedType_ExportedMethod\(\)`,
+ `Method example.`,
+ },
+ nil,
+ },
+
+ // Package example.
+ {
+ "package example",
+ []string{p, `Example`},
+ []string{
+ `fmt.Println\("Package example output"\)`,
+ `Output: Package example output`,
+ },
+ []string{`func Example\(\)`},
+ },
+
+ // Multiline output example.
+ {
+ "multiline output example",
+ []string{p, `Example_multiline`},
+ []string{
+ `fmt.Println\("Multiline\\nexample\\noutput"\)`,
+ "Output: \nMultiline\nexample\noutput",
+ },
+ []string{`Output: Multiline example output`},
+ },
+
+ // Type example.
+ {
+ "type example",
+ []string{p, `ExampleExportedType`},
+ []string{
+ `fmt.Println\("Type example output"\)`,
+ `Output: Type example output`,
+ },
+ []string{`func ExampleExportedType\(\)`},
+ },
+
+ // Function example.
+ {
+ "function example",
+ []string{p, `ExampleExportedFunc`},
+ []string{
+ `fmt.Println\("Function example output"\)`,
+ `Output: Function example output`,
+ },
+ []string{
+ `func ExampleExportedFunc\(\)`,
+ `fmt.Println\("Function example two output"\)`,
+ `Output: Function example two output`,
+ },
+ },
+
+ // Function example two.
+ {
+ "function example",
+ []string{p, `ExampleExportedFunc_two`},
+ []string{
+ `fmt.Println\("Function example two output"\)`,
+ `Output: Function example two output`,
+ },
+ []string{
+ `func ExampleExportedFunc_two\(\)`,
+ `fmt.Println\("Function example output"\)`,
+ `Output: Function example output`,
+ },
+ },
+
+ // Method example.
+ {
+ "method example",
+ []string{p, `ExampleExportedType_ExportedMethod`},
+ []string{
+ `fmt.Println\("Method example output"\)`,
+ `Output: Method example output`,
+ },
+ []string{`func ExampleExportedType_ExportedMethod\(\)`},
+ },
+
+ // Playable example.
+ {
+ "playable example",
+ []string{p, `Example_playable`},
+ []string{
+ `package main`,
+ `func main\(\) {`,
+ `fmt.Println\("Playable example output"\)`,
+ `}`,
+ `Output: Playable example output`,
+ },
+ []string{`func Example_playable\(\)`},
+ },
+
// Case matching off.
{
"case matching off",
diff --git a/src/cmd/go/internal/doc/pkg.go b/src/cmd/go/internal/doc/pkg.go
index 3c36d0e05c..c531595f86 100644
--- a/src/cmd/go/internal/doc/pkg.go
+++ b/src/cmd/go/internal/doc/pkg.go
@@ -139,17 +139,16 @@ func (pkg *Package) Fatalf(format string, args ...any) {
// we can then use to generate documentation.
func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Package {
// include tells parser.ParseDir which files to include.
- // That means the file must be in the build package's GoFiles or CgoFiles
- // list only (no tag-ignored files, tests, swig or other non-Go files).
+ // That means the file must be in the build package's GoFiles, CgoFiles,
+ // TestGoFiles or XTestGoFiles list only (no tag-ignored files, swig or
+ // other non-Go files).
include := func(info fs.FileInfo) bool {
- for _, name := range pkg.GoFiles {
- if name == info.Name() {
- return true
- }
- }
- for _, name := range pkg.CgoFiles {
- if name == info.Name() {
- return true
+ files := [][]string{pkg.GoFiles, pkg.CgoFiles, pkg.TestGoFiles, pkg.XTestGoFiles}
+ for _, f := range files {
+ for _, name := range f {
+ if name == info.Name() {
+ return true
+ }
}
}
return false
@@ -159,13 +158,9 @@ func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Packag
if err != nil {
log.Fatal(err)
}
- // Make sure they are all in one package.
if len(pkgs) == 0 {
log.Fatalf("no source-code package in directory %s", pkg.Dir)
}
- if len(pkgs) > 1 {
- log.Fatalf("multiple packages in directory %s", pkg.Dir)
- }
astPkg := pkgs[pkg.Name]
// TODO: go/doc does not include typed constants in the constants
@@ -180,7 +175,16 @@ func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Packag
if showSrc {
mode |= doc.PreserveAST // See comment for Package.emit.
}
- docPkg := doc.New(astPkg, pkg.ImportPath, mode)
+ var allGoFiles []*ast.File
+ for _, p := range pkgs {
+ for _, f := range p.Files {
+ allGoFiles = append(allGoFiles, f)
+ }
+ }
+ docPkg, err := doc.NewFromFiles(fset, allGoFiles, pkg.ImportPath, mode)
+ if err != nil {
+ log.Fatal(err)
+ }
typedValue := make(map[*doc.Value]bool)
constructor := make(map[*doc.Func]bool)
for _, typ := range docPkg.Types {
@@ -568,6 +572,7 @@ func (pkg *Package) packageDoc() {
pkg.valueSummary(pkg.doc.Vars, false)
pkg.funcSummary(pkg.doc.Funcs, false)
pkg.typeSummary()
+ pkg.exampleSummary(pkg.doc.Examples, false)
}
if !short {
@@ -650,11 +655,27 @@ func (pkg *Package) funcSummary(funcs []*doc.Func, showConstructors bool) {
if isExported(fun.Name) {
if showConstructors || !pkg.constructor[fun] {
pkg.Printf("%s\n", pkg.oneLineNode(fun.Decl))
+ if showEx {
+ pkg.exampleSummary(fun.Examples, false)
+ }
}
}
}
}
+// exampleSummary prints a one-line summary for each example.
+func (pkg *Package) exampleSummary(exs []*doc.Example, showDoc bool) {
+ if !showEx {
+ return
+ }
+ for _, ex := range exs {
+ pkg.Printf(indent+"func Example%s()\n", ex.Name)
+ if showDoc && ex.Doc != "" {
+ pkg.ToText(&pkg.buf, ex.Doc, indent+indent, indent+indent)
+ }
+ }
+}
+
// typeSummary prints a one-line summary for each type, followed by its constructors.
func (pkg *Package) typeSummary() {
for _, typ := range pkg.doc.Types {
@@ -676,8 +697,10 @@ func (pkg *Package) typeSummary() {
for _, constructor := range typ.Funcs {
if isExported(constructor.Name) {
pkg.Printf(indent+"%s\n", pkg.oneLineNode(constructor.Decl))
+ pkg.exampleSummary(constructor.Examples, false)
}
}
+ pkg.exampleSummary(typ.Examples, false)
}
}
}
@@ -728,6 +751,40 @@ func (pkg *Package) findTypes(symbol string) (types []*doc.Type) {
return
}
+// findExamples finds any examples that describe the symbol.
+func (pkg *Package) findExamples(symbol string) (examples []*doc.Example) {
+ if !strings.HasPrefix(symbol, "Example") {
+ return
+ }
+ symbol = strings.TrimPrefix(symbol, "Example")
+ var all []*doc.Example
+ all = append(all, pkg.doc.Examples...)
+ for _, typ := range pkg.doc.Types {
+ all = append(all, typ.Examples...)
+ for _, fun := range typ.Funcs {
+ all = append(all, fun.Examples...)
+ }
+ for _, fun := range typ.Methods {
+ all = append(all, fun.Examples...)
+ }
+ }
+ for _, fun := range pkg.doc.Funcs {
+ all = append(all, fun.Examples...)
+ }
+
+ // always include unexported in below match(), so Example_one
+ // is still matched despite trimming the Example_ part.
+ u := unexported
+ unexported = true
+ for _, ex := range all {
+ if match(symbol, ex.Name) {
+ examples = append(examples, ex)
+ }
+ }
+ unexported = u
+ return
+}
+
// findTypeSpec returns the ast.TypeSpec within the declaration that defines the symbol.
// The name must match exactly.
func (pkg *Package) findTypeSpec(decl *ast.GenDecl, symbol string) *ast.TypeSpec {
@@ -750,6 +807,11 @@ func (pkg *Package) symbolDoc(symbol string) bool {
// Symbol is a function.
decl := fun.Decl
pkg.emit(fun.Doc, decl)
+ pkg.exampleSummary(fun.Examples, true)
+ found = true
+ }
+ for _, ex := range pkg.findExamples(symbol) {
+ pkg.emitExample(ex)
found = true
}
// Constants and variables behave the same.
@@ -866,6 +928,7 @@ func (pkg *Package) typeDoc(typ *doc.Type) {
pkg.valueSummary(typ.Consts, true)
pkg.valueSummary(typ.Vars, true)
pkg.funcSummary(typ.Funcs, true)
+ pkg.exampleSummary(typ.Examples, false)
pkg.funcSummary(typ.Methods, true)
}
}
@@ -1025,6 +1088,7 @@ func (pkg *Package) printMethodDoc(symbol, method string) bool {
if match(method, meth.Name) {
decl := meth.Decl
pkg.emit(meth.Doc, decl)
+ pkg.exampleSummary(meth.Examples, true)
found = true
}
}
@@ -1166,3 +1230,33 @@ func simpleFold(r rune) rune {
r = r1
}
}
+
+// emitExample prints an example and its output.
+func (pkg *Package) emitExample(ex *doc.Example) {
+ pkg.buf.printed = true // omit the package clause
+ var err error
+ if ex.Play != nil {
+ err = format.Node(&pkg.buf, pkg.fs, ex.Play)
+ } else {
+ // If code is an *ast.BlockStmt, trim the braces and indentation
+ // by just printing the enclosed List of ast.[]Stmt.
+ b, ok := ex.Code.(*ast.BlockStmt)
+ if !ok {
+ err = format.Node(&pkg.buf, pkg.fs, ex.Code)
+ } else {
+ err = format.Node(&pkg.buf, pkg.fs, b.List)
+ }
+ }
+ if err != nil {
+ log.Fatal(err)
+ }
+ if ex.Output != "" {
+ pkg.newlines(2)
+ pkg.Printf("Output: ")
+ if strings.Count(ex.Output, "\n") > 1 {
+ pkg.newlines(1)
+ }
+ pkg.Printf("%s", ex.Output)
+ }
+ pkg.newlines(1)
+}
diff --git a/src/cmd/go/internal/doc/testdata/pkg_test.go b/src/cmd/go/internal/doc/testdata/pkg_test.go
new file mode 100644
index 0000000000..0b4e62e008
--- /dev/null
+++ b/src/cmd/go/internal/doc/testdata/pkg_test.go
@@ -0,0 +1,47 @@
+// Copyright 2026 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package pkg
+
+import "fmt"
+
+// Package example.
+func Example() {
+ fmt.Println("Package example output")
+ // Output: Package example output
+}
+
+// Function example.
+func ExampleExportedFunc() {
+ fmt.Println("Function example output")
+ // Output: Function example output
+}
+
+// Function example two.
+func ExampleExportedFunc_two() {
+ fmt.Println("Function example two output")
+ // Output: Function example two output
+}
+
+// Type example.
+func ExampleExportedType() {
+ fmt.Println("Type example output")
+ // Output: Type example output
+}
+
+// Method example.
+func ExampleExportedType_ExportedMethod() {
+ fmt.Println("Method example output")
+ // Output: Method example output
+}
+
+// Multiline example.
+func Example_multiline() {
+ fmt.Println("Multiline\nexample\noutput")
+ // Output:
+ // Multiline
+ // example
+ // output
+}
+
diff --git a/src/cmd/go/internal/doc/testdata/play_test.go b/src/cmd/go/internal/doc/testdata/play_test.go
new file mode 100644
index 0000000000..6b51089503
--- /dev/null
+++ b/src/cmd/go/internal/doc/testdata/play_test.go
@@ -0,0 +1,13 @@
+// Copyright 2026 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package pkg_test
+
+import "fmt"
+
+// Playable example.
+func Example_playable() {
+ fmt.Println("Playable example output")
+ // Output: Playable example output
+}