diff options
| author | Brad Fitzpatrick <bradfitz@tailscale.com> | 2026-04-22 21:08:16 +0000 |
|---|---|---|
| committer | Brad Fitzpatrick <bradfitz@tailscale.com> | 2026-04-22 21:53:46 +0000 |
| commit | e369a547bd6afdce66cc3f504d40e847a70dd3db (patch) | |
| tree | 279ad9997c27dd4f719a2067a5f0abbcdbe58e7e /tempfork/pkgdoc/pkgdoc.go | |
| parent | f289f7e77c66c4870897eb21f1b3a0ba6e83bb14 (diff) | |
| download | tailscale-bradtest.tar.xz tailscale-bradtest.zip | |
misc/genreadme,tempfork/pkgdoc,tsnet: generate README.md files from godocbradtest
Adds a CI check and Makefile target to keep opted-in directories'
README.md files in sync with their package godoc. For now tsnet (and
its sub-packages under tsnet/example) is the only opted-in tree. The
list of directories lives in misc/genreadme/genreadme.go as
defaultRoots, so CI and humans both just run `make genreadme` with no
arguments.
The genreadme workflow runs `make genreadme` and fails if any
README.md is out of date, pointing the user at the same command.
Along the way:
- tempfork/pkgdoc now emits Markdown instead of plain text: headings
become level-2 with no {#hdr-...} anchors, and [Symbol] doc links
resolve to pkg.go.dev URLs, including for symbols in the current
package (which the default Printer would otherwise emit as bare
#Name fragments with no backing anchor in a README). Parsing no
longer uses parser.ImportsOnly, so doc.Package knows the package's
symbols and can resolve [Symbol] links at all.
- genreadme also emits a pkg.go.dev Go Reference badge at the top of
a library package's README; suppressed for package main.
- tsnet/tsnet.go's package godoc is expanded in idiomatic godoc
syntax — [Type], [Type.Method], reference-style [link]: URL
definitions — rather than Markdown-flavored [text](url) or
backtick-quoted identifiers, so that both pkg.go.dev and the
generated README.md render cleanly from a single source.
Fixes #19431
Fixes #19483
Fixes #19470
Change-Id: Iade21dc87921ab2bbb65951e5fb373b597fed29e
Signed-off-by: Walter Poupore <walterp@tailscale.com>
Diffstat (limited to 'tempfork/pkgdoc/pkgdoc.go')
| -rw-r--r-- | tempfork/pkgdoc/pkgdoc.go | 46 |
1 files changed, 41 insertions, 5 deletions
diff --git a/tempfork/pkgdoc/pkgdoc.go b/tempfork/pkgdoc/pkgdoc.go index 1868b028e..cab38dd48 100644 --- a/tempfork/pkgdoc/pkgdoc.go +++ b/tempfork/pkgdoc/pkgdoc.go @@ -13,6 +13,7 @@ import ( "go/ast" "go/build" "go/doc" + "go/doc/comment" "go/parser" "go/token" "io" @@ -46,6 +47,31 @@ func (pkg *Package) ToText(w io.Writer, text, prefix, codePrefix string) { w.Write(pr.Text(d)) } +// ToMarkdown parses the godoc comment text and writes a Markdown rendering to w +// suitable for a repository README.md: top-level sections become ## headings +// without per-heading anchor IDs, and [Symbol] doc links resolve to pkg.go.dev, +// including for symbols in the current package (which the default printer would +// otherwise emit as bare #Name fragments with no backing anchor). +func (pkg *Package) ToMarkdown(w io.Writer, text string) { + d := pkg.doc.Parser().Parse(text) + pr := pkg.doc.Printer() + pr.HeadingLevel = 2 + pr.HeadingID = func(*comment.Heading) string { return "" } + pr.DocLinkBaseURL = "https://pkg.go.dev" + pr.DocLinkURL = func(link *comment.DocLink) string { + importPath := link.ImportPath + if importPath == "" { + importPath = pkg.doc.ImportPath + } + name := link.Name + if link.Recv != "" { + name = link.Recv + "." + name + } + return "https://pkg.go.dev/" + importPath + "#" + name + } + w.Write(pr.Markdown(d)) +} + // pkgBuffer is a wrapper for bytes.Buffer that prints a package clause the // first time Write is called. type pkgBuffer struct { @@ -85,7 +111,10 @@ func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Packag return slices.Contains(pkg.GoFiles, info.Name()) || slices.Contains(pkg.CgoFiles, info.Name()) } fset := token.NewFileSet() - pkgs, err := parser.ParseDir(fset, pkg.Dir, include, parser.ParseComments|parser.ImportsOnly) + // Parse declarations (not just imports) so that doc.Package knows the + // package's symbols; the Markdown printer needs this to resolve + // [Symbol] doc links in package comments. + pkgs, err := parser.ParseDir(fset, pkg.Dir, include, parser.ParseComments) if err != nil { log.Fatal(err) } @@ -144,10 +173,10 @@ func (pkg *Package) newlines(n int) { } } -// packageDoc prints the docs for the package. +// packageDoc prints the docs for the package as Markdown. func (pkg *Package) packageDoc() { pkg.Printf("") // Trigger the package clause; we know the package exists. - pkg.ToText(&pkg.buf, pkg.doc.Doc, "", indent) + pkg.ToMarkdown(&pkg.buf, pkg.doc.Doc) pkg.newlines(1) pkg.bugs() @@ -175,8 +204,12 @@ func (pkg *Package) bugs() { } } -// PackageDoc generates documentation for a package in the given directory. -func PackageDoc(dir string) ([]byte, error) { +// PackageDoc generates Markdown documentation for the package in the given +// directory. importPath is the full Go import path of that package (e.g. +// "tailscale.com/tsnet"); it's used to render [Symbol] doc links to the +// right pkg.go.dev URL. If importPath is empty, build.ImportDir's guess +// is used (typically "." for module-based repos). +func PackageDoc(dir, importPath string) ([]byte, error) { var buf bytes.Buffer var writer io.Writer = &buf @@ -188,6 +221,9 @@ func PackageDoc(dir string) ([]byte, error) { } return nil, err } + if importPath != "" { + buildPackage.ImportPath = importPath + } userPath := dir pkg := parsePackage(writer, buildPackage, userPath) |
