Uber Go Style Guide, enforced by golangci-lint.
Disclaimer:
uberlintis an independent open source project. It is not affiliated with, endorsed by, sponsored by, or connected to Uber Technologies, Inc.
uberlint is a focused set of Go analyzers for teams that like the Uber Go Style Guide but do not want to rely on memory, review comments, or tribal knowledge to enforce it. It catches the style-guide rules that common golangci-lint setups usually miss: panic usage, oversized channel buffers, zero-valued enums, pointer-to-interface types, raw sync/atomic calls, and more.
The goal is simple: make the preferred Go style automatic, reviewable, and CI-friendly.
Code review should be about design, correctness, and trade-offs, not repeatedly asking for &T{} instead of new(T) or for enum values to start at iota + 1.
uberlint gives you:
- Uber Go Style Guide coverage for rules that are missing or only partially covered by standard linters.
- Native
golangci-lintintegration through the module plugin system. - Small, explainable analyzers built on
golang.org/x/tools/go/analysis. - Rule selection through plugin settings, so teams can adopt strictness gradually.
- Standalone runner support for development and debugging outside
golangci-lint.
| Linter | Checks |
|---|---|
ifaceptr |
Avoid pointers to interfaces. |
chansize |
Keep channel buffer sizes at 0 or 1. |
enumstart |
Start iota-style enums at a non-zero value. |
nopanic |
Avoid panic() in production code. |
atomicstd |
Prefer typed sync/atomic values over raw atomic functions. |
stringbytes |
Avoid repeated []byte("literal") conversions inside loops. |
vartype |
Omit redundant top-level var types when the initializer already provides the type. |
globalprefix |
Prefix unexported top-level globals with _. |
nilslice |
Return nil instead of empty slices where nil is sufficient. |
rawstring |
Prefer raw string literals when they avoid quote escaping without changing semantics. |
zerovar |
Use var x T for zero-value structs instead of x := T{}. |
newref |
Prefer &T{} over new(T) for struct references. |
publicembed |
Avoid embedded fields in exported structs. |
embedlayout |
Keep embedded fields grouped before named fields. |
localvar |
Prefer := for local variables with inferred types. |
zerofields |
Omit explicit zero-value fields in struct literals. |
mapinit |
Prefer make(map[K]V) for empty map initialization. |
constprintf |
Use constants for printf format strings stored outside call sites. |
nakedparams |
Avoid calls with multiple naked literal arguments. |
timefield |
Require unit suffixes for serialized numeric time fields. |
Use uberlint in your Go project as a golangci-lint module plugin. The setup has two files:
.custom-gcl.ymltellsgolangci-linthow to build a custom binary withuberlintinside..golangci.ymlenables theuberlintrules your project wants to run.
After that, you run ./custom-gcl run ./... instead of the stock golangci-lint run ./....
Requirements:
- Go
- git
golangci-lintv2 withgolangci-lint custom
In the Go project you want to lint, add .custom-gcl.yml:
version: v2.0.0
name: custom-gcl
plugins:
- module: github.com/aagumin/uberlint
version: v0.1.6If you are trying a local checkout before a release is available, use path instead:
version: v2.0.0
name: custom-gcl
plugins:
- module: github.com/aagumin/uberlint
path: /absolute/path/to/uberlintThen add .golangci.yml:
version: "2"
linters:
default: none
enable:
- uberlint
settings:
custom:
uberlint:
type: "module"
description: "Uber Go Style Guide linter"Build and run:
golangci-lint custom -v
./custom-gcl run ./...The first command reads .custom-gcl.yml and builds a local custom-gcl binary. The second command runs that binary with the uberlint plugin enabled by .golangci.yml.
Important: enable uberlint, not individual analyzer names like nopanic or ifaceptr. The module plugin registers one golangci-lint linter named uberlint; that linter runs the analyzers listed below.
golangci-lint sees one custom linter named uberlint. Individual checks are selected through plugin settings.
To run only specific analyzers:
version: "2"
linters:
default: none
enable:
- uberlint
settings:
custom:
uberlint:
type: "module"
description: "Uber Go Style Guide linter"
settings:
enable:
- nopanic
- ifaceptr
- enumstartTo run all analyzers except a few:
version: "2"
linters:
enable:
- uberlint
settings:
custom:
uberlint:
type: "module"
description: "Uber Go Style Guide linter"
settings:
disable:
- nakedparams
- constprintfUse either enable or disable, not both. Unknown analyzer names fail the run with an explicit error.
This means the custom-gcl binary was built without the uberlint module plugin registration. Check these points:
.golangci.ymlmust enableuberlint, not individual analyzer names:
linters:
enable:
- uberlint
settings:
custom:
uberlint:
type: "module"
description: "Uber Go Style Guide linter"- Rebuild the custom binary after changing
.custom-gcl.ymlor upgradinguberlint:
rm -f ./custom-gcl
golangci-lint custom -v
./custom-gcl run ./...- If diagnostics still look stale after upgrading, for example
zerovar: zerovar: ..., clear thegolangci-lintcache and run again:
./custom-gcl cache clean
./custom-gcl run ./...- If you are testing a local checkout, make sure
.custom-gcl.ymlpoints to that checkout withpath.
For CI, build the custom binary and run it like any other golangci-lint command:
name: lint
on:
pull_request:
push:
branches: [main]
jobs:
golangci-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
- name: Install golangci-lint
run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- name: Build custom golangci-lint
run: golangci-lint custom -v
- name: Run linters
run: ./custom-gcl run ./...For faster CI, cache Go modules and the golangci-lint cache using your CI provider's normal Go caching setup.
For development, you can also run uberlint without golangci-lint:
go run ./cmd/uberlint ./...This is useful when developing new analyzers or debugging a rule. For normal project adoption, prefer the golangci-lint module plugin path so output, CI behavior, exclusions, and editor integrations stay consistent with the rest of your lint setup.
Use normal golangci-lint suppression comments when a rule is intentionally not applicable:
//nolint:enumstart // zero is the wire-compatible default
const (
Unknown Status = iota
Ready
Failed
)Keep suppressions narrow and documented. A suppression should explain why this instance is exceptional, not why the rule is inconvenient.
uberlint currently ships 20 analyzers covering the first two implementation slices of the Uber Go Style Guide. The analyzers are tested with analysistest, and the project also includes a standalone multichecker binary for local development.
Run the project tests with:
go test ./...- Uber Go Style Guide: https://github.com/uber-go/guide/blob/master/style.md
- golangci-lint module plugin docs: https://golangci-lint.run/docs/plugins/module-plugins/