diff --git a/docs/go-doc-best-practices.md b/docs/go-doc-best-practices.md index 39701d9..fc48aee 100644 --- a/docs/go-doc-best-practices.md +++ b/docs/go-doc-best-practices.md @@ -13,6 +13,119 @@ This guide describes the best practices for writing Go documentation comments (d - **Focus on Behavior and Use**: Explain what it *does* and how it’s used—not how it’s implemented. - **Markdown-Like Style**: Use formatting like lists or bold when helpful (when using `godoc` renderers that support this). +### RULE go-doc/exported-item-must-have-comment (MUST) + +**Owner**: godoc-assistant +**Applies when**: a Go file defines an exported (capitalized) function, method, type, interface, struct, struct field, constant, or variable without a preceding `//` doc comment. +**Enforcement**: judgment (mechanical follow-up: enable `revive`'s `exported` rule or `golangci-lint`'s `revive` linter with `exported` enabled — these flag every exported identifier without a doc comment) +**Why**: `pkg.go.dev` renders every exported identifier — undocumented items show up as bare signatures with no context. Consumers reading the API page can't tell what `func Process(*Order) error` does without reading the source. Doc comments are a contract surface; missing them shifts the burden from the author (who knows) to every reader (who doesn't). + +#### Bad + +```go +type Order struct { + ID string + Total Price + Customer Customer +} + +func (o *Order) Apply(d Discount) Price { ... } +``` + +#### Good + +```go +// Order represents a customer's purchase, holding line items and the +// computed total before any discounts are applied. +type Order struct { + ID string + Total Price + Customer Customer +} + +// Apply reduces the order's total by the given discount and returns +// the new total. Apply mutates the order in place. +func (o *Order) Apply(d Discount) Price { ... } +``` + +### RULE go-doc/comment-starts-with-name (MUST) + +**Owner**: godoc-assistant +**Applies when**: an exported identifier has a doc comment whose first word does not match the identifier's name exactly (case-sensitive). +**Enforcement**: judgment (mechanical follow-up: no standard linter catches this — `revive`'s `exported` rule only flags *missing* doc comments, not first-word mismatch. Best path is an ast-grep pattern over `func_declaration` / `type_declaration` + adjacent comment text, comparing the first whitespace-delimited word against the identifier name — see PR #11 recipe for the surrounding-comment pattern shape) +**Why**: `godoc` and `pkg.go.dev` build the docs from the first sentence of each comment and key it by the identifier name. When the comment starts with a different word, the rendered docs read as "X creates a new …" attached to identifier `Y` — confusing and grep-hostile. Starting with the identifier's name also forces the author to think about what the thing *is*, not what they wish it did. + +#### Bad + +```go +// Creates a new order with the given customer. // first word ≠ "NewOrder" +func NewOrder(c Customer) *Order { ... } +``` + +#### Good + +```go +// NewOrder returns an order initialised with the given customer +// and a zeroed total. +func NewOrder(c Customer) *Order { ... } +``` + +### RULE go-doc/package-comment-in-doc-go (SHOULD) + +**Owner**: godoc-assistant +**Applies when**: a Go package's package-level comment lives in a regular source file (`.go`) rather than in a dedicated `doc.go`. +**Enforcement**: judgment (filename presence + first-comment-block check) +**Why**: When the package comment lives in `order.go`, deleting / refactoring / renaming that file silently strips the package documentation. `doc.go` is a convention — every reader knows where to find package-level docs, and refactors of business-logic files don't accidentally damage the docs. + +#### Bad + +```go +// order.go +// Package orders manages customer purchases. +package orders + +type Order struct { ... } // delete this file → package comment gone +``` + +#### Good + +```go +// doc.go +// Package orders manages customer purchases, applying discounts and +// computing totals against a Customer's history. +package orders +``` + +```go +// order.go +package orders + +type Order struct { ... } +``` + +### RULE go-doc/third-person-no-signature-repeat (SHOULD) + +**Owner**: godoc-assistant +**Applies when**: a doc comment uses first-person ("I", "we", "our") OR repeats the function signature verbatim in the prose ("takes an `int` and returns a `string`" when the signature already says `func F(int) string`). +**Enforcement**: judgment (prose linters can flag first-person; signature-repeat is semantic and needs review) +**Why**: First-person breaks the API documentation register — readers want a neutral spec, not the author's internal monologue. Signature-repeat is noise: the type checker already publishes the signature; the comment's job is the *behavior* the signature can't express (preconditions, side effects, error semantics, units). + +#### Bad + +```go +// I wrote this to take two ints and return their sum as an int. +// We use it in the totals calculator. +func Add(a, b int) int { ... } +``` + +#### Good + +```go +// Add returns the sum of a and b. Overflow wraps per Go's int semantics; +// callers needing checked addition should use math/bits. +func Add(a, b int) int { ... } +``` + --- ## 🔍 Per Construct Guidelines diff --git a/rules/index.json b/rules/index.json index bab2b22..2e594d2 100644 --- a/rules/index.json +++ b/rules/index.json @@ -71,6 +71,42 @@ "level": "SHOULD", "owner": "go-context-assistant" }, + { + "anchor": "go-doc/comment-starts-with-name", + "applies_when": "an exported identifier has a doc comment whose first word does not match the identifier's name exactly (case-sensitive).", + "doc_path": "docs/go-doc-best-practices.md", + "enforcement": "judgment (mechanical follow-up: no standard linter catches this — `revive`'s `exported` rule only flags *missing* doc comments, not first-word mismatch. Best path is an ast-grep pattern over `func_declaration` / `type_declaration` + adjacent comment text, comparing the first whitespace-delimited word against the identifier name — see PR #11 recipe for the surrounding-comment pattern shape)", + "id": "go-doc/comment-starts-with-name", + "level": "MUST", + "owner": "godoc-assistant" + }, + { + "anchor": "go-doc/exported-item-must-have-comment", + "applies_when": "a Go file defines an exported (capitalized) function, method, type, interface, struct, struct field, constant, or variable without a preceding `//` doc comment.", + "doc_path": "docs/go-doc-best-practices.md", + "enforcement": "judgment (mechanical follow-up: enable `revive`'s `exported` rule or `golangci-lint`'s `revive` linter with `exported` enabled — these flag every exported identifier without a doc comment)", + "id": "go-doc/exported-item-must-have-comment", + "level": "MUST", + "owner": "godoc-assistant" + }, + { + "anchor": "go-doc/package-comment-in-doc-go", + "applies_when": "a Go package's package-level comment lives in a regular source file (`.go`) rather than in a dedicated `doc.go`.", + "doc_path": "docs/go-doc-best-practices.md", + "enforcement": "judgment (filename presence + first-comment-block check)", + "id": "go-doc/package-comment-in-doc-go", + "level": "SHOULD", + "owner": "godoc-assistant" + }, + { + "anchor": "go-doc/third-person-no-signature-repeat", + "applies_when": "a doc comment uses first-person (\"I\", \"we\", \"our\") OR repeats the function signature verbatim in the prose (\"takes an `int` and returns a `string`\" when the signature already says `func F(int) string`).", + "doc_path": "docs/go-doc-best-practices.md", + "enforcement": "judgment (prose linters can flag first-person; signature-repeat is semantic and needs review)", + "id": "go-doc/third-person-no-signature-repeat", + "level": "SHOULD", + "owner": "godoc-assistant" + }, { "anchor": "go-errors/inner-closure-no-double-wrap", "applies_when": "an inner closure (passed to `db.Update`, `filepath.WalkDir`, or similar callback APIs) calls `errors.Wrap`/`errors.Wrapf` while the surrounding function ALSO wraps the closure's return value.",