go fix Modernizers & go vet Analyzers
go vet catches suspicious code the compiler accepts, and go fix applies automated rewrites - including Go 1.26 modernizers that update legacy patterns to current idioms.
Summary
Static analysis in Go starts in the stdlib: go vet ships analyzers for common mistakes like misformatted Printf verbs and unreachable code.
go fix runs fixers registered with the same analysis framework, rewriting sources in place when safe.
Go 1.26 expands go fix with modernizers for patterns such as old loop variable capture and deprecated APIs.
Teams layer golangci-lint and staticcheck on top for broader rules, but vet/fix remain the zero-config baseline every module can run.
Recipe
Quick-reference recipe card - copy-paste ready.
# Run default vet analyzers on all packages
go vet ./...
# Preview modernizer rewrites without writing files
go fix -diff ./...
# Apply modernizers and review in git diff
go fix ./...
# Run a specific analyzer (example: printf)
go vet -printf=false -assign ./...When to reach for this:
- Run
go vet ./...in CI on every PR alongside tests. - Run
go fix -diffafter upgrading Go to see modernizer suggestions before applying. - Add custom analyzers via
golang.org/x/tools/go/analysisfor org-specific rules. - Reach for golangci-lint when you need dozens of linters with shared config.
Working Example
// before modernizer: loop variable capture in closure (pre-1.22 style risk)
package worker
func Jobs() []func() int {
ids := []int{1, 2, 3}
var fns []func() int
for _, id := range ids {
fns = append(fns, func() int { return id })
}
return fns
}go vet ./...
go fix -diff ./...
go fix ./...
go test ./...// after go fix modernizer (illustrative - exact rewrite depends on analyzer version)
package worker
func Jobs() []func() int {
ids := []int{1, 2, 3}
var fns []func() int
for _, id := range ids {
id := id // modernizer may insert shadow copy where still needed
fns = append(fns, func() int { return id })
}
return fns
}What this demonstrates:
go vetflags constructs that compile but behave oddly.go fix -diffshows patches before you commit them.- Modernizers target idioms that changed across Go releases.
- Tests confirm behavior after automated edits.
Deep Dive
How It Works
- Both commands load packages with
go/packages, type-check them, then run analysis passes registered viaanalysis.Analyzer. go vetreports diagnostics to stderr and exits non-zero when issues exist.go fixruns analyzers that implementSuggestedFixand writes updated source files.- The analysis driver respects build tags and module boundaries the same way
go testdoes.
Built-in vet Checks (sample)
| Analyzer theme | Example issue |
|---|---|
printf | Wrong verb for argument type |
assign | Self-assignment or useless assignment |
unreachable | Code after return/panic |
structtag | Malformed JSON or DB struct tags |
errorsas | errors.As target not pointer to error type |
loopclosure | Loop variable captured by goroutine or defer |
Modernizers in Go 1.26
- Invoked through
go fix(not a separate binary). - Focus on language and stdlib idiom upgrades rather than style nitpicks.
- Always review
git diff- fixers assume common cases, not every domain invariant. - Run on a clean branch so you can bisect if a rewrite changes behavior.
Go Notes
# Install extended analyzers (staticcheck) for local runs
go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck ./...
# golangci-lint aggregates vet, staticcheck, gosec, and more
golangci-lint run ./...Gotchas
- Applying
go fixwithout tests - modernizers can change semantics at edge cases. Fix: fullgo test ./...and targeted benchmarks after every fix pass. - Assuming vet replaces linters - vet is intentionally small. Fix: add staticcheck and golangci-lint for security and performance rules.
- Disabling vet globally in CI - teams forget re-enable. Fix: disable specific analyzers with flags, not
go vetitself. - Custom analyzers without tests - false positives erode trust. Fix: ship analyzer tests with
analysistestpackage. - Running fix on generated code - generators overwrite fixes next run. Fix: exclude
vendor/and generated dirs; fix inputs or templates instead. - Ignoring vet errors in cgo files - cgo confuses some checks. Fix: narrow package scope or use build tags to split cgo packages.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
staticcheck | Deeper bug and performance checks | You only need the stdlib baseline |
golangci-lint | One config for dozens of linters | Tiny repos where go vet is enough |
go fix modernizers | Bulk upgrades after Go version bump | Hand-written legacy code with subtle invariants |
| IDE inspections (gopls) | Immediate feedback while typing | CI still needs command-line gates |
FAQs
What is the difference between go vet and staticcheck?
go vet ships a small stdlib set focused on definite bugs.
staticcheck adds hundreds of checks maintained by the Go community.
Does go fix run gofmt?
Fixers may format changed regions.
Run gofmt or goimports on the whole tree if style drifts.
Can I write my own analyzer?
Yes.
Use golang.org/x/tools/go/analysis and register it with a driver or custom multichecker.
Why does vet pass but golangci-lint fails?
Linters enable stricter or additional rules.
Treat golangci-lint as a superset, not a replacement for tests.
What are modernizers?
go fix analyzers bundled with newer Go releases that rewrite outdated patterns to current idioms.
They expand over time; read release notes when upgrading.
Should fix run in CI?
Usually no - CI should be read-only.
Run fix locally or in a dedicated bot PR, then commit results.
How do I silence a vet false positive?
Refactor if possible.
For exceptions, use //nolint comments only with team policy and a tracked reason.
Does vet need network?
No for your module if dependencies are cached.
Analyzers only type-check local and module sources.
Can vet analyze tests?
Yes.
It loads _test.go files the same way go test does.
How does this relate to gopls?
gopls runs many analyzers in the editor for diagnostics.
go vet and CI linters keep team-wide gates consistent.
Related
- The go Command: Build, Test, and Module Lifecycle - shared analysis loader
- Go Toolchain Basics - introductory
go vetexample - go test, list & generate - verify fixes with tests
- Go Toolchain Best Practices - CI lint ordering
- Build Tags & Conditional Compilation - tag-aware analysis scope
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).