style
esc
  • Loading…

Check itGo Style Guide lint kit

Conventions for writing, structuring, testing and shipping Go 1.27 services, agents and CLIs that stay simple, explicit and easy to read.

Go 1.27golangci-lintslog · synctest
Download the kit 73 checks · rule IDs on every finding

Checks a Go codebase against the Go Style Guide and reports every finding with its rule ID, linked to the rule:

internal/invoice/service.go:42:10: MUST GO-08.3: say what this function was doing, without "failed to" or "error": "failed to save invoice: %w" [chopper]
internal/httpapi/server.go:17:2: MUST NOT GO-14.14: use one http.Client per dependency with a Timeout, not http.Get [chopper]
internal/postgres/store.go:88:3: MUST GO-15.8: rows.Err must be checked [golangci-lint:rowserrcheck]

It runs two kinds of check:

  • golangci-lint v2 with the guide's .golangci.yml (Appendix A): the linters, plus the gofumpt and goimports formatters (chapter 5). It runs once per Go module (go.mod) under the paths, and findings are mapped to the rule they enforce; any other golangci-lint finding is reported as GO-05.7, since CI fails on all of them. A module that declares golangci-lint with the tool directive in go.mod (GO-02.3) is linted with its own go tool golangci-lint; otherwise golangci-lint on PATH is used. With neither, the kit checks formatting with gofmt and says so.
  • The guide's own checks for rules golangci-lint does not express: the cmd/ and internal/ layout, package and file names, import groups, receivers, ctx first and error last, error strings and errors.AsType, panics and os.Exit outside main, logging with slog, os.Getenv outside config, HTTP servers and clients, SQL built with fmt.Sprintf, test conventions, doc comments, TODO owners, and go.mod, go.sum, Taskfile and README checks. checks.json lists them all.

The repository's own .golangci.yml is used when it has one (the nearest one above each module); otherwise the kit's copy (the guide's appendix, verbatim) is, with example.com/project in goimports.local-prefixes replaced by the module path. go vet runs inside golangci-lint (govet); govulncheck needs the network and stays in your task lint.

Run it

Needs Python 3.9+, and Go with golangci-lint v2 for the tool checks. The guide's template already declares golangci-lint in go.mod, so go tool builds it on first use; elsewhere install it from golangci-lint.run.

python3 chopper_lint.py                          # the current folder
python3 chopper_lint.py internal cmd             # some folders
python3 chopper_lint.py --build-tags integration # lint integration-tagged files too
python3 chopper_lint.py --strict                 # fail on SHOULD and AVOID too
python3 chopper_lint.py --no-golangci            # the guide's own checks only
python3 chopper_lint.py --list-checks            # what it checks

Folders named vendor, testdata, third_party, node_modules, bin and dist are skipped, as are hidden folders. Generated files (// Code generated ... DO NOT EDIT.) are only checked for their name. Run go mod download (and go generate) first, so golangci-lint can type-check every package; the kit notes any package it could not load.

Exit status: 1 when a MUST or MUST NOT rule is broken (any finding with --strict), else 0.

In GitHub Actions

- uses: actions/checkout@v4
- uses: carson-hopper/chopper.dev/content/styleguides/go/lint@main
  with:
    paths: .
    # go-version-file: services/api/go.mod   # where the Go version is read from (default go.mod)
    # build-tags: integration

The action installs Go with actions/setup-go (from go-version-file, or go-version) and golangci-lint v2.15.0 with golangci/golangci-lint-action (install-only), both pinned to a commit; set golangci-lint-version to change it. Modules that declare golangci-lint with go tool use their own pinned version instead. Findings appear as annotations on the pull request. Pin @main to a commit SHA for reproducible runs.

Silencing a finding

On the same line or the line above, with a reason:

os.Exit(code) // chopper-lint: allow GO-08.14 (the CLI's run helper owns the exit code)

For a whole file: // chopper-lint: allow-file GO-08.14. It works in go.mod too, for the module checks. An allow comment also silences a golangci-lint finding mapped to that rule; for the linters themselves, use //nolint:<linter> // <reason> (GO-05.8).