devtools/cmd/dumpdoc: add symbol doc

Add the documentation for all the exported symbols to the output file.

Change-Id: I25270765d81f78c3ff558243fea726fa68da65a3
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/566495
kokoro-CI: kokoro <noreply+kokoro@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Tatiana Bradley <tatianabradley@google.com>
diff --git a/devtools/cmd/dumpdoc/dumpdoc.go b/devtools/cmd/dumpdoc/dumpdoc.go
index 4998b5f..0a904c8 100644
--- a/devtools/cmd/dumpdoc/dumpdoc.go
+++ b/devtools/cmd/dumpdoc/dumpdoc.go
@@ -1,4 +1,4 @@
-// Copyright 2021 The Go Authors. All rights reserved.
+// Copyright 2024 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.
 
@@ -7,11 +7,16 @@
 package main
 
 import (
+	"bytes"
 	"context"
 	"database/sql"
 	"encoding/gob"
 	"flag"
 	"fmt"
+	"go/ast"
+	"go/doc"
+	"go/printer"
+	"go/token"
 	"io"
 	"os"
 	"strings"
@@ -69,16 +74,6 @@
 	}
 }
 
-type PackageDoc struct {
-	ImportPath     string
-	ModulePath     string
-	Version        string
-	NumImporters   int
-	PackageDoc     string
-	ReadmeFilename *string
-	ReadmeContents *string
-}
-
 func write(ctx context.Context, db *database.DB, filename string) error {
 	query := fmt.Sprintf(`
 		SELECT s.package_path, s.module_path, s.version, s.imported_by_count,
@@ -134,13 +129,71 @@
 	if err != nil {
 		return err
 	}
-	if strings.TrimSpace(dpkg.Doc) == "" {
-		return nil
-	}
 	pd.PackageDoc = dpkg.Doc
+	var sds []SymbolDoc
+	for _, v := range dpkg.Consts {
+		sds = append(sds, valueSymbolDoc(gpkg.Fset, v))
+	}
+	for _, v := range dpkg.Vars {
+		sds = append(sds, valueSymbolDoc(gpkg.Fset, v))
+	}
+	for _, t := range dpkg.Types {
+		sd := SymbolDoc{
+			Names: []string{t.Name},
+			Decl:  formatDecl(gpkg.Fset, t.Decl),
+			Doc:   t.Doc,
+		}
+		sds = append(sds, sd)
+		for _, v := range t.Consts {
+			sds = append(sds, valueSymbolDoc(gpkg.Fset, v))
+		}
+		for _, v := range t.Vars {
+			sds = append(sds, valueSymbolDoc(gpkg.Fset, v))
+		}
+		for _, f := range t.Funcs {
+			// No prefix: these are top-level functions that return the type.
+			sds = append(sds, functionSymbolDoc("", gpkg.Fset, f))
+		}
+		for _, f := range t.Methods {
+			sds = append(sds, functionSymbolDoc(t.Name, gpkg.Fset, f))
+		}
+		// TODO: Examples
+	}
+	for _, f := range dpkg.Funcs {
+		sds = append(sds, functionSymbolDoc("", gpkg.Fset, f))
+		// TODO:Examples
+	}
+	pd.SymbolDocs = sds
 	return nil
 }
 
+func valueSymbolDoc(fset *token.FileSet, v *doc.Value) SymbolDoc {
+	return SymbolDoc{
+		Names: v.Names,
+		Decl:  formatDecl(fset, v.Decl),
+		Doc:   v.Doc,
+	}
+}
+
+func functionSymbolDoc(prefix string, fset *token.FileSet, f *doc.Func) SymbolDoc {
+	if prefix != "" {
+		prefix += "."
+	}
+	return SymbolDoc{
+		Names: []string{prefix + f.Name},
+		Decl:  formatDecl(fset, f.Decl),
+		Doc:   f.Doc,
+	}
+
+}
+
+func formatDecl(fset *token.FileSet, decl ast.Decl) string {
+	p := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 4}
+	var b bytes.Buffer
+	p.Fprint(&b, fset, decl)
+	return b.String()
+}
+
 func read(filename string) error {
 	f, err := os.Open(filename)
 	if err != nil {
@@ -157,16 +210,26 @@
 		if err != nil {
 			return err
 		}
-		pd.PackageDoc = trunc(pd.PackageDoc)
-		fmt.Printf("%s (%s@%s):\n", pd.ImportPath, pd.ModulePath, pd.Version)
-		fmt.Printf("    %d importers\n", pd.NumImporters)
-		fmt.Printf("     pkg doc: %q\n", pd.PackageDoc)
-		if pd.ReadmeFilename != nil && pd.ReadmeContents != nil {
-			*pd.ReadmeContents = trunc(*pd.ReadmeContents)
-			fmt.Printf("     readme (from %s): %q\n", *pd.ReadmeFilename, *pd.ReadmeContents)
-		}
+		pd.Show()
 	}
+}
 
+func (pd PackageDoc) Show() {
+	pd.PackageDoc = trunc(pd.PackageDoc)
+	fmt.Printf("%s (%s@%s):\n", pd.ImportPath, pd.ModulePath, pd.Version)
+	fmt.Printf("    %d importers\n", pd.NumImporters)
+	fmt.Printf("     pkg doc: %q\n", pd.PackageDoc)
+	if pd.ReadmeFilename != nil && pd.ReadmeContents != nil {
+		*pd.ReadmeContents = trunc(*pd.ReadmeContents)
+		fmt.Printf("     readme (from %s): %q\n", *pd.ReadmeFilename, *pd.ReadmeContents)
+	}
+	fmt.Printf("    symbols\n:")
+	for _, sd := range pd.SymbolDocs {
+		fmt.Printf("\tNames: %v\n", sd.Names)
+		fmt.Printf("\tDecl: %s\n", sd.Decl)
+		fmt.Printf("\tDoc: %q\n", trunc(sd.Doc))
+		fmt.Println()
+	}
 }
 
 func trunc(s string) string {
diff --git a/devtools/cmd/dumpdoc/types.go b/devtools/cmd/dumpdoc/types.go
new file mode 100644
index 0000000..73d07be
--- /dev/null
+++ b/devtools/cmd/dumpdoc/types.go
@@ -0,0 +1,22 @@
+// Copyright 2024 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 main
+
+type PackageDoc struct {
+	ImportPath     string
+	ModulePath     string
+	Version        string
+	NumImporters   int
+	PackageDoc     string
+	SymbolDocs     []SymbolDoc
+	ReadmeFilename *string
+	ReadmeContents *string
+}
+
+type SymbolDoc struct {
+	Names []string // consts and vars may have multiple names
+	Decl  string   // the declaration as a string
+	Doc   string
+}