Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
261 changes: 261 additions & 0 deletions skills/go-documentation/SKILL.md

Large diffs are not rendered by default.

367 changes: 367 additions & 0 deletions skills/go-documentation/references/comment-syntax.md

Large diffs are not rendered by default.

174 changes: 174 additions & 0 deletions skills/go-documentation/references/examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Testable examples

Go's `testing` package lets you write **example functions** that are compiled (and optionally run) by `go test` and rendered on pkg.go.dev as usage examples. They are the most reliable form of API documentation because they cannot drift away from the code — renaming a function or changing its signature breaks the example at the next test run, surfacing the doc-rot before it reaches users.

Specification: https://pkg.go.dev/testing#hdr-Examples

## Table of contents

- [When to add an example](#when-to-add-an-example)
- [File placement](#file-placement)
- [Naming](#naming)
- [Multiple examples per symbol](#multiple-examples-per-symbol)
- [Output verification](#output-verification)
- [Whole-file examples](#whole-file-examples)
- [Common mistakes](#common-mistakes)

## When to add an example

Add an example whenever the API has a non-trivial usage pattern that prose alone won't communicate clearly — typical cases:

- A type that requires a setup sequence (`New`, configure, then use).
- A function whose return value must be consumed in a particular way (`defer resp.Body.Close()`).
- A package whose primary entry point is one of several functions, and the example shows the "happy path".
- Any signature that takes a callback or option struct where the call site is more revealing than the type signature.

Skip examples for trivial getters, accessors, or functions whose name and signature already say everything (`func Len() int`).

## File placement

Examples live in test files (filename ending in `_test.go`), in the same directory as the package being documented. Two choices for the package clause:

```go
package mypkg // internal test package — can access unexported identifiers
package mypkg_test // external test package — same import path as a user
```

Prefer `mypkg_test` for examples. Forcing the example to import the package the way a user would catches mistakes like relying on unexported helpers, and the rendered example on pkg.go.dev includes the `import "mypkg"` line that real users will need to write.

`go test` compiles `_test.go` files when running tests for the package, so a misnamed identifier in an example fails the test run — that's the safety net that keeps examples from rotting.

## Naming

The example's function name selects which symbol it attaches to in the rendered documentation:

| Function name | Attached to |
| -------------------- | -------------------------------------------- |
| `Example` | The package as a whole |
| `ExampleF` | Top-level function `F` |
| `ExampleT` | Type `T` |
| `ExampleT_M` | Method `M` on type `T` |

Use the exact identifier capitalization. `ExampleHttpClient` does **not** attach to `HTTPClient`.

## Multiple examples per symbol

To attach more than one example to the same symbol, append `_suffix` to the function name. The suffix has one strict rule: **it must begin with a lower-case letter.** `ExampleClient_basic` works; `ExampleClient_Basic` does not — the parser reads `Basic` as if it were a type or method named `Basic` on `Client`, which doesn't exist, and the example silently misattaches.

```go
func Example_basic() { ... } // package-level, "basic" variant
func ExampleClient_pooled() { ... } // Client type, "pooled" variant
func ExampleClient_Do_retry(){ ... } // Client.Do method, "retry" variant
```

pkg.go.dev renders the suffix as a label next to the example (capitalized for display: "Basic", "Pooled", "Retry"). Use the suffix to describe the variant — `_retry`, `_streaming`, `_withTimeout` — not to number them sequentially. Numbered variants (`_1`, `_2`) read as bug reports rather than documentation.

## Output verification

An example can declare expected output with a trailing comment. `go test` captures the example's stdout, trims leading/trailing whitespace from both sides, and compares.

### `// Output:`

Exact match (line-ordered):

```go
func ExampleHello() {
fmt.Println("hello")
// Output: hello
}

func ExampleSalutations() {
fmt.Println("hello, and")
fmt.Println("goodbye")
// Output:
// hello, and
// goodbye
}
```

The `// Output:` comment must be the final block in the function body. If it doesn't match, the test fails with a diff.

### `// Unordered output:`

For examples where the output order is non-deterministic (e.g., map iteration, goroutines):

```go
func ExamplePerm() {
for _, value := range Perm(5) {
fmt.Println(value)
}
// Unordered output:
// 4
// 2
// 1
// 3
// 0
}
```

The comparison ignores line order but still requires every expected line to appear exactly once.

### No output comment

> Example functions without output comments are compiled but not executed.

This is the right choice when:

- The example uses external resources (network, filesystem) that aren't appropriate for `go test`.
- The example's purpose is to demonstrate the API shape, and asserting the output would just be busywork.
- The example is illustrative and doesn't actually run end-to-end (e.g., it shows what a configuration call site looks like).

Don't add a misleading `// Output:` with fabricated values to "look complete" — `go test` will fail the moment someone runs it.

## Whole-file examples

A `_test.go` file is rendered as a single self-contained example block on pkg.go.dev when it contains:

1. Exactly one `Example` function, and
2. At least one other top-level declaration (function, type, var, or const), and
3. No `Test*` or `Benchmark*` functions.

This is useful when the example needs supporting types or helpers that would clutter the example body — they get rendered alongside the function, as a single coherent listing:

```go
// example_server_test.go
package http_test

import (
"fmt"
"log"
"net/http"
)

type countHandler struct {
n int
}

func (h *countHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.n++
fmt.Fprintf(w, "count is %d\n", h.n)
}

func ExampleHandler() {
http.Handle("/count", &countHandler{})
log.Fatal(http.ListenAndServe(":8080", nil))
}
```

If any of the three conditions is violated (e.g., you add a second `Example` function or any `Test*` function), pkg.go.dev falls back to rendering only the `Example` function body without the supporting declarations — usually not what you want. Either keep the file pure or split helpers into a separate file.

## Common mistakes

**`// Output:` not the last block.** Anything after the Output comment is ignored. If you add code after it expecting it to run, it won't.

**Wrong suffix casing.** `ExampleClient_Pooled` attaches to a non-existent `Pooled` method on `Client`, not to `Client` with a "Pooled" label. Suffixes start lower-case.

**Forgetting the package suffix in external tests.** A file in `package mypkg_test` can't see unexported names. If an example relied on an unexported helper, switching from `package mypkg` to `package mypkg_test` will break it.

**Examples calling `os.Exit` or `log.Fatal`.** Either makes the test process exit, which fails the test run for the entire package. Use `fmt.Println` for visible output and let errors propagate as normal Go values.

**Tabs vs spaces in expected output.** `go test` trims leading/trailing whitespace on each side of the comparison, but indentation inside the expected output is matched as written. Inconsistent tabs/spaces in the `// Output:` lines fail the assertion. Let `gofmt` normalize them.

**Examples that depend on map ordering.** Maps iterate in undefined order. Use `// Unordered output:` or sort the keys before printing.

**Examples that import the package as `.`** (dot import). The rendered example loses the `import "mypkg"` line, which is the most useful part of the rendering. Use a normal import.
150 changes: 150 additions & 0 deletions skills/go-documentation/references/pkgsite-preview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Previewing documentation locally with pkgsite

`pkgsite` is the same Go program that powers https://pkg.go.dev, packaged so you can run it locally against an unpublished module. Use it to iterate on doc comments and see exactly how they will render once published — particularly important when comments contain anything richer than plain prose (links, lists, code blocks, examples), since the HTML rendering can diverge from the plain-text `go doc` view.

Upstream: https://pkg.go.dev/golang.org/x/pkgsite/cmd/pkgsite. The source is at https://github.com/golang/pkgsite.

## Table of contents

- [Install](#install)
- [Basic usage](#basic-usage)
- [Flags](#flags)
- [Iterating on a single module](#iterating-on-a-single-module)
- [Multi-module workspaces](#multi-module-workspaces)
- [Serving from the module cache or proxy](#serving-from-the-module-cache-or-proxy)
- [Refreshing after edits](#refreshing-after-edits)
- [Common gotchas](#common-gotchas)

## Install

```bash
go install golang.org/x/pkgsite/cmd/pkgsite@latest
```

The binary lands in `$(go env GOBIN)` (or `$(go env GOPATH)/bin` if `GOBIN` is unset). Make sure that directory is on `$PATH`.

Verify:

```bash
pkgsite -help
```

## Basic usage

From the root of a Go module, run:

```bash
cd /path/to/your/module
pkgsite
```

pkgsite reads `go.mod`, generates documentation for the main module and its dependencies, and serves them at http://localhost:8080. Open that URL in a browser.

To open the browser automatically:

```bash
pkgsite -open
```

The module's docs are reachable at `http://localhost:8080/<module-path>` — for a module declared as `module example.com/foo`, that is `http://localhost:8080/example.com/foo`.

## Flags

Verified against the source at `cmd/pkgsite/main.go`:

| Flag | Default | Description |
| -------------- | ------------------ | ---------------------------------------------------------------------------- |
| `-http` | `localhost:8080` | HTTP service address to listen on. Use `-http :8080` to bind on all interfaces. |
| `-open` | `false` | Open a browser window to the server's address on startup. |
| `-cache` | `false` | Fetch modules from the local module cache (`$GOMODCACHE`). |
| `-cachedir` | `""` | Override the module cache directory; defaults to `go env GOMODCACHE`. |
| `-proxy` | `false` | Fetch from `GOPROXY` if not found locally. Useful to view third-party docs offline-ish. |
| `-list` | `true` | For each path argument, serve all modules in the build list (i.e., the module and its required dependencies). Set `-list=false` to serve only the requested modules. |
| `-gorepo` | `""` | Path to a local checkout of the Go repo, used to speed up standard library rendering. Without it, pkgsite clones the Go repo on first use of any stdlib package, which is slow. |
| `-gopath_mode` | `false` | Treat local modules as relative to `$GOPATH/src`. Only relevant for pre-modules code. |
| `-dev` | `false` | Developer mode for working on pkgsite itself: reload templates on each request, serve non-minified assets. Not needed for normal documentation preview. |
| `-static` | `"static"` | Path to static assets. Defaults work; only change if running from a non-standard install. |

## Iterating on a single module

The recommended loop for working on documentation:

```bash
cd /path/to/module
pkgsite -open
```

This launches the server and opens the module's page. Leave the server running. Edit doc comments, save, then reload the browser tab — pkgsite re-reads the source on every request, so changes appear immediately. There is no file watcher; you trigger refreshes manually via the browser.

The exception is template/asset changes — those require restarting the server (or running with `-dev`, which auto-reloads templates). For normal doc comment editing, no restart needed.

## Multi-module workspaces

For a multi-module project using a Go workspace (`go.work`), pkgsite picks up every module listed in the workspace file:

```bash
go work init ./module-a ./module-b
pkgsite
```

Each module is reachable at its own URL on the local server. This is the right setup when working on documentation for a module that imports a sibling module — both can be edited and previewed side by side without publishing intermediate versions.

## Serving from the module cache or proxy

By default, pkgsite serves docs for the module in the current directory plus its dependencies as resolved by `go list`. With `-cache` or `-proxy`, it also (or instead) serves modules from your `$GOMODCACHE` or the public proxy:

```bash
# Serve a specific cached module
pkgsite -cache example.com/some/dep

# Pull any module from the proxy on demand
pkgsite -proxy
```

When either flag is set, pkgsite does **not** look for a module in the current directory. To serve both the current module and proxied modules, list them explicitly:

```bash
pkgsite -cache -proxy . example.com/other/module
```

The `.` argument tells pkgsite to also include the module rooted at the current directory.

## Refreshing after edits

- **Doc comment edits**: just reload the browser tab. pkgsite re-parses the source on each request.
- **`go.mod` changes** (new dependency, version bump): no restart needed; pkgsite re-resolves the build list per request.
- **Template/CSS/JS changes to pkgsite itself**: restart the server, or run with `-dev` for hot-reload.
- **Switching modules in workspace mode**: the file watcher does not pick up new `go.work` entries; restart the server when editing `go.work`.

## Common gotchas

**Port already in use.** If 8080 is taken, pick another: `pkgsite -http localhost:6060`.

**Standard library docs are slow on first launch.** Without `-gorepo`, pkgsite clones the Go source on first stdlib reference, which takes minutes. If you frequently view stdlib pages, set `-gorepo` to a local Go checkout. If you only care about your own module's docs, this isn't an issue — pkgsite doesn't pre-fetch stdlib unless requested.

**Internal packages are visible locally.** pkgsite serves `internal/` packages on the local server (since you have source access), but pkg.go.dev hides them. A doc link to an `internal/` package will render locally but break once published. Either move the symbol out of `internal/` or rephrase the doc to not link there.

**`go.mod` module path must match the source location.** pkgsite uses the `module` directive to construct URLs. If `go.mod` says `module example.com/foo` but the directory is `~/code/bar`, the docs are still served under `/example.com/foo`. The directory name doesn't matter; the `module` line does.

**Cached pages.** Browser cache can mask doc updates if you reload the same URL with no change. Hard-reload (Cmd-Shift-R / Ctrl-Shift-R) if a comment edit doesn't appear to take effect.

**Dependency docs show "no documentation available".** pkgsite renders documentation from the source it can find. If a dependency is in `vendor/` but you're running without `-mod=vendor`, or if a dependency hasn't been fetched into the cache yet, the page is empty. Run `go mod download` first to populate the cache.

**Render differs between `go doc` and pkgsite.** This is expected for comments with markup — `go doc` outputs plain text, pkgsite outputs HTML. Use `go doc` for quick "did I associate the comment with the right declaration" checks, and pkgsite for the final visual review.

## Verification before publication

Run this checklist before tagging a release:

1. `pkgsite -open` in the module root.
2. Click through every exported package and confirm:
- The package overview is present and starts with `Package <name>`.
- The package summary appears in search-style listings (the first sentence).
- Every exported symbol has a doc comment (no "no documentation available" entries).
- Doc links resolve (no literal `[Name]` brackets in the rendered text).
- Examples render with `Output:` sections where expected.
- Code blocks render preformatted, not as paragraph text.
3. Skim the README rendering on the package landing page.
4. Verify the rendered page matches the symbols and version you intend to publish.

What you see in pkgsite is what users will see on pkg.go.dev. The two render from the same source.
Loading