I wrote a linter!
Creating a linter in Go is surprisingly easy! I wrote one and integrated it with golangci-lint to diagnose "fat contexts". This post documents the process.
A few weeks ago, I published an article titled Context-induced performance bottleneck in Go, in which I discuss a misuse of Go's context.Context
. I didn't find any linter already handling this in golangci-lint
, our meta linter of choice, so I decided to create one!
π A new linter
πΊοΈ Exploration
As it turns out, creating a linter is surprisingly easy. Since my intention was to add it to golangci-lint
, I headed to their website and found they had published some guidance. This post from one of golangci-lint
's maintainers was especially helpful and well written.
golangci-lint
.After a few hours of tinkering, I ended up with fatcontext
:
You can use it today, prebuilt binaries are available on each release.
The meat of the linter is in pkg/analyzer/analyzer.go
and it fits in ~115 lines of code, including ~30 lines just to suggest a fix (which golangci-lint
doesn't even support yet).
Thanks to go/analysis
, with few efforts on my part, you can use fatcontext
:
- as a standalone binary,
- with
go vet
, - with
golangci-lint
, - and probably others.
golangci-lint
even requires writing new linters using go/analysis
.
π§ͺ Testing
Testing it was also simple (and built-in). You have to write a small test file which will trigger go/analysis
test runner, and some source code as test cases. The test source should be annotated with special comments signaling to go/analysis/analysistest
that we expect a report on a line. It will then ensure that your analysis is complete and precise:
- complete: it didn't miss any expected report
- precise: it didn't emit any false positive
Given how easy it is to write these tests, it's completely possible to create a linter with TDD, which I didn't do this time but would probably do next time.
π§Ί Integration
So, now we have a proper linter, but it's not yet integrated with our toolchain of choice. From here, I had two possibilities: integrating this with golangci-lint
using either their module plugin system or through Go's plugin system and keep it to ourselves, or integrating this in golangci-lint
's public version. Given the issue it detects isn't specific to OVHcloud, I decided to take the public route.
Integrating with golangci-lint
requires opening a pull request, following some advice found in the documentation:
I had a friendly and productive ping-pong with the maintainers who commented with a helpful checklist. It pushed me to add a GitHub Action workflow to run tests automatically and publish new releases of the linter, very easy thanks to goreleaser
. I was fortunate enough to be available at the same time as the maintainers, so our exchange was fluid even if happening in the pull request discussion only.
A few days later, the PR was merged! I'm happy to report that fatcontext
is available since golangci-lint@v1.58.0
π
π― Conclusion
All in all, this was a very pleasant experience, full of learning and first times. If you also have an itch that can be solved by writing a linter, I highly suggest trying! At worst you'll probably learn some new stuff, at best you'll create a helpful tool for the Go community π
That's it for today, thank you for reading! See you in a future post π