Code Style¶
TL;DR
Format with gofumpt, lint with golangci-lint (34 linters), write
Godoc on every exported symbol, wrap errors with %w, keep dependencies
minimal, and run make fmt + make lint before every push.
Who is this for?
All contributors. These conventions are enforced in CI and locally via
make all.
Formatting¶
gofumpt¶
gouvernante uses gofumpt -- a stricter
superset of gofmt. Key differences from plain gofmt:
- No empty lines at the start or end of function bodies.
- No empty lines around a lone statement in a block.
- Grouped
var/constblocks are kept compact.
Apply formatting:
make fmt
Check formatting without modifying files (used in CI):
make fmt-check
Import grouping¶
Use goimports ordering -- three groups separated by blank lines:
import (
"fmt"
"os"
"github.com/goccy/go-yaml"
"gouvernante/pkg/lockfile"
"gouvernante/pkg/rules"
)
- Standard library imports.
- Third-party imports (vetted external dependencies).
- Local module imports.
gofumpt with the module-path: gouvernante setting in .golangci.yml
enforces this three-group layout automatically.
Linting¶
golangci-lint¶
The project runs golangci-lint with 34
linters enabled. Configuration lives in .golangci.yml at the repository
root.
make lint
Notable enabled linters include:
| Linter | Purpose |
|---|---|
staticcheck |
Advanced static analysis (SA/S/ST/QF checks). |
errcheck |
Ensures returned errors are handled. |
govet |
Reports suspicious constructs (printf args, struct tags, etc.). |
ineffassign |
Detects assignments to variables that are never read. |
unparam |
Flags unused function parameters. |
gocritic |
Opinionated style and performance checks. |
gosec |
Security-oriented checks (hardcoded credentials, weak crypto). |
misspell |
Catches common spelling mistakes in comments and strings. |
revive |
Configurable replacement for golint. |
bodyclose |
Ensures HTTP response bodies are closed. |
staticcheck¶
staticcheck runs as part of golangci-lint but can also be invoked
standalone for more detailed output:
staticcheck ./...
Fix every finding -- the project does not maintain a baseline of accepted warnings.
Godoc Comments¶
Every exported symbol -- functions, types, constants, variables, and methods -- must have a Godoc comment that starts with the symbol name:
// PackageEntry represents a single resolved package from a lockfile.
type PackageEntry struct {
Name string
Version string
}
// BuildPackageIndex constructs an index of rules keyed by package name for
// O(1) lookup during scanning.
func BuildPackageIndex(rules []Rule) PackageIndex {
// ...
}
Rules:
- Start with the symbol name (
// BuildPackageIndex ...). - Use complete sentences.
- Document non-obvious parameters and return values.
- Unexported helpers benefit from comments too, but it is not enforced by the linter.
Error Handling¶
Wrap with %w¶
Always wrap errors so callers can use errors.Is / errors.As:
if err != nil {
return nil, fmt.Errorf("loading rule %s: %w", path, err)
}
No panics¶
Never call panic in library code. Return an error instead. The only
acceptable panic is in main() or test helpers via t.Fatal.
Sentinel errors¶
Define package-level sentinel errors for conditions callers need to branch on:
// ErrUnsupportedLockfile indicates that no parser is registered for the
// detected lockfile format.
var ErrUnsupportedLockfile = errors.New("unsupported lockfile format")
Check them with errors.Is:
if errors.Is(err, lockfile.ErrUnsupportedLockfile) {
log.Printf("skipping: %v", err)
return
}
Dependency Policy¶
gouvernante keeps external dependencies minimal and vetted. The scanning and
matching engine uses only the Go standard library. Format parsers may use
vetted libraries where robustness outweighs purity (e.g., goccy/go-yaml
for pnpm lockfile parsing).
| Scope | Policy |
|---|---|
| Scanning & matching engine | Go standard library only |
| Format parsers | Vetted libraries allowed |
| Test infrastructure | Test-only dependencies allowed |
| CLI framework | flag package (standard library) |
All dependencies are vendored in vendor/ and committed to the repository. After
modifying go.mod, run make vendor to update the vendor directory. CI verifies
vendor consistency via make vendor-check.
Before adding a new dependency, open an issue to discuss scope and justification. See the Minimal Dependencies ADR for the full rationale, vendoring policy, and license compliance.
Makefile Targets¶
| Target | Command | Description |
|---|---|---|
make all |
fmt + lint + cover + build + licenses + test-integration |
Full pre-push validation. |
make build |
Cross-compile all platforms | Binaries in dist/binaries/. |
make test |
go test -mod=vendor -race ./... |
Run tests with race detector. |
make cover |
go test -mod=vendor -race -coverprofile=... ./... |
Generate coverage report. |
make fmt |
gofumpt -w . |
Format all Go files in place. |
make fmt-check |
gofumpt -d . (fail on diff) |
Check formatting without writing. |
make lint |
golangci-lint run ./... |
Run the full linter suite. |
make vendor |
go mod vendor |
Update vendored dependencies. |
make vendor-check |
go mod verify |
Verify vendor directory consistency. |
make licenses |
scripts/licenses.sh |
Generate third-party license report. |
make test-integration |
Docker build + run | End-to-end integration test with planted IOCs. |
Run make all before pushing. CI runs the same targets and will reject any
commit that fails.
Self-Assessment Checklist¶
-
make fmtproduces no diff. -
make lintreports zero findings. - Every exported symbol has a Godoc comment starting with its name.
- Errors are wrapped with
fmt.Errorf("context: %w", err). - No
paniccalls outside ofmain()or test helpers. - No unapproved external dependencies added to
go.mod. -
vendor/is up to date (make vendor && make vendor-check). -
make allpasses.
Next Steps¶
- Testing -- test conventions that complement these style rules.
- Adding Parsers -- apply these patterns when writing a new lockfile parser.
- Writing Rules -- JSON style conventions for rule files.