From 44769b36f1da0f99aaa9014ecc9c8aac3b7b3b8d Mon Sep 17 00:00:00 2001 From: Benjamin Borbe Date: Tue, 2 Jun 2026 13:11:58 +0200 Subject: [PATCH 1/2] feat(godoc): bootstrap 4 rule blocks in go-doc-best-practices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructures the godoc style guide with canonical `### RULE` blocks for its enforceable conventions. Mirrors PRs #2-5, #8, #10, #14. Rules added (rules/index.json: 46 -> 50): - go-doc/exported-item-must-have-comment (MUST, judgment) Every exported function/type/const/var needs a doc comment. Mechanical follow-up: revive's 'exported' rule. - go-doc/comment-starts-with-name (MUST, judgment) Doc comment first word must match the identifier name. Mechanical follow-up: revive (or ast-grep via the PR #11 recipe). - go-doc/package-comment-in-doc-go (SHOULD, judgment) Package comment belongs in doc.go to survive refactors of feature files. - go-doc/third-person-no-signature-repeat (SHOULD, judgment) No first-person prose; don't restate the signature. All examples generic (Order, Customer, Discount, Add) — no trading domain. All rule IDs unique against the 46 existing entries. godoc-assistant agent exists at agents/godoc-assistant.md. Pre-emptive checks (lessons from PRs #6, #8, #14): - No personal vault paths - No trading-domain terms - All cross-refs resolve - make build-index regenerated; check-index target passes --- docs/go-doc-best-practices.md | 113 ++++++++++++++++++++++++++++++++++ rules/index.json | 36 +++++++++++ 2 files changed, 149 insertions(+) diff --git a/docs/go-doc-best-practices.md b/docs/go-doc-best-practices.md index 39701d9..f0e5bf5 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, 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: `revive`'s `exported` rule enforces this; can also be captured with an ast-grep pattern over `func_declaration` + adjacent comment text — see PR #11 recipe for the struct-literal 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..b877fda 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: `revive`'s `exported` rule enforces this; can also be captured with an ast-grep pattern over `func_declaration` + adjacent comment text — see PR #11 recipe for the struct-literal 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, 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.", From f1806b25d7e01aaebd76ea0afe4a8a957aef8954 Mon Sep 17 00:00:00 2001 From: Benjamin Borbe Date: Tue, 2 Jun 2026 13:19:35 +0200 Subject: [PATCH 2/2] fix(godoc): address bot review on PR #15 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CRITICAL: comment-starts-with-name's enforcement claimed revive's 'exported' rule enforces first-word match — factually wrong. revive's exported lint only flags MISSING doc comments, not first-word mismatch against the identifier name. Reworded to make clear that no standard linter catches this; ast-grep over func_declaration + adjacent comment text is the actual mechanical path. MAJOR: exported-item-must-have-comment's applies_when enumeration omitted exported struct fields, which revive's exported rule does catch. Added 'struct field' to the list. --- docs/go-doc-best-practices.md | 4 ++-- rules/index.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/go-doc-best-practices.md b/docs/go-doc-best-practices.md index f0e5bf5..fc48aee 100644 --- a/docs/go-doc-best-practices.md +++ b/docs/go-doc-best-practices.md @@ -16,7 +16,7 @@ This guide describes the best practices for writing Go documentation comments (d ### 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, constant, or variable without a preceding `//` doc 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. **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). @@ -52,7 +52,7 @@ func (o *Order) Apply(d Discount) Price { ... } **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: `revive`'s `exported` rule enforces this; can also be captured with an ast-grep pattern over `func_declaration` + adjacent comment text — see PR #11 recipe for the struct-literal pattern shape) +**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 diff --git a/rules/index.json b/rules/index.json index b877fda..2e594d2 100644 --- a/rules/index.json +++ b/rules/index.json @@ -75,14 +75,14 @@ "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: `revive`'s `exported` rule enforces this; can also be captured with an ast-grep pattern over `func_declaration` + adjacent comment text — see PR #11 recipe for the struct-literal pattern shape)", + "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, constant, or variable without a preceding `//` doc 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",