Skip to content
Merged
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
67 changes: 67 additions & 0 deletions .claude/rules/technical-writing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
paths:
- "**/*.go"
- "**/*.md"
---

# Technical writing style (go-openapi)

Applies to every committed comment, commit message, README and doc-site page.

The standard is Ernest Gowers, *Plain Words*: **be short, be simple, be human.**
His worked example is the whole rule:

DON'T Was this the realisation of an anticipated liability?
DO Did you expect to have to do this?

The abstract nouns carry no information; the concrete verb carries all of it.

## Two tests

**The grep test.** Does the sentence contain something a reader can search for — an
identifier, a file, a flag, an error, a number with a unit? Prose that names nothing has
described the code without pointing at it.

**The quotability test.** A sentence that would survive being quoted on its own is too
pleased with itself. Rewrite it until it merely sounds true.

## Never define by inversion

The worst and most frequent fault. A copula whose subject or predicate is a wh-clause
promises a definition and delivers a metaphor. Both directions are banned:

DON'T Coverage is what says which templates a suite never reaches.
DON'T What is lost is the doc comment.
DO Coverage records which templates the suite never executed.
DO A synthesized type loses its doc comment.

The rewrite is mechanical: find the verb hiding inside the wh-clause and make it the main
verb of the sentence.

`which is why` pointing back at a fact just stated is legitimate, and rationed — one per
comment is plenty.

## The rest

- **Name the thing.** `WithRoots`, not "the option that scopes a repository". Name the
error, the file, the flag, the upstream package, the constant.
- **Statement, not aphorism.** State mechanism and effect. Never close a paragraph on a
maxim: the reflex lands hardest on a closing sentence.
- **Keep a subject.** "New returns an error if the source is unreadable", not "What a
source leaves out is settled where it is declared".
- **Plain verbs.** add, fix, return, parse, reject, cap, prune, record. Code does not say,
judge, grant, refuse, know, mean to, or reach for. `report` is fine when something
genuinely reports.
- **Keep the numbers.** Sizes with units, counts, ratios, advisory ids. `286 -> 178 KiB`,
`GHSA-v2xp-g8xf-22pf`. Dropping them for a smoother sentence loses information.
- **Be human.** Address the reader where there is advice: "Use `WithRoot` to confine local
loading." Admit the awkward thing rather than smoothing it over.

## Self-check

# definition by inversion, both directions
grep -rnE '\b(is|are) (what|where) [a-z]' --include='*.go' --include='*.md' .
grep -rnE '(^|\. )What [a-z][a-z ,-]{3,50} (is|are) ' --include='*.go' --include='*.md' .

Subtract the legitimate `which/that/this/it is what` before judging the first one.
Neither grep is a verdict — they find one fault out of six. The others need reading.
2 changes: 1 addition & 1 deletion object_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ func (o *objectValidator) validatePropertiesSchema(val map[string]any, res *Resu
}

// located on the object that lacks the property: the property itself
// has no node to point at, and the object is what has to be amended
// has no node to point at, and the object has to be amended
res.addErrorsAt(o.Path, errors.Required(o.Path.child(k).dotted(), o.In, v))
}
}
Expand Down
2 changes: 1 addition & 1 deletion path.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
//
// Validators build a location by appending tokens as they descend into
// properties and array items, then render it only when they report an error.
// Keeping the tokens apart until then is what makes it possible to produce a
// Keeping the tokens apart until then makes it possible to produce a
// valid [RFC 6901] JSON pointer: a token is escaped when it is rendered, and
// the separator can never be confused with a token that contains one.
//
Expand Down
2 changes: 1 addition & 1 deletion pools.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
//
// Validation allocates a validator per schema node and a result per check, so
// the same handful of types are built and thrown away constantly. Recycling
// them is what keeps validating a large specification affordable.
// them keeps validating a large specification affordable.
//
// Build with the "poolsdebug" tag to have every borrow and redeem tracked:
// misuse then panics where it happens rather than corrupting a pool, and
Expand Down
2 changes: 1 addition & 1 deletion spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ func (s *SpecValidator) SetContinueOnErrors(c bool) {
func (s *SpecValidator) validateNonEmptyPathParamNames() *Result {
res := validatorPools.results.Borrow()
if s.spec.Spec().Paths == nil {
// There is no Paths object: the document itself is what lacks it, so
// There is no Paths object: the document itself lacks it, so
// there is no node below it to point at
res.addErrorsAt(rootPath(), noValidPathMsg())

Expand Down
Loading