Vinego is a new set of linters built on the Go analysis.Analyzer framework. Many of the linters focus on increasing strictness around variable initialization and type safety. They fall somewhere in-between "drop these into your codebase with no changes" and "rewrite all your code to conform to weird new language-extra conventions" in terms of invasiveness.
Each linter is an analysis.Analyzer and they're aggregated as a golangci-lint plugin. Individual non-opt-in linters can be enabled via the golangci-lint settings. The analyzers should work with any analysis.Analyzer framework though.
-
allfieldsConfirms that all required fields are explicitly initialized in struct literals.
Add a comment before a struct type like:
// check:allfields type MyStruct { X int Y string Z bool `optional:""` }Then, if you do (for instance):
m := MyStruct{X: 4}you'll get an error that
Yis missing. -
varinitEnabled with
enable_varinit: truein.vinego.yaml.Checks that all variables have been explicitly initialized (with a value) before usage.
For example:
var i int if something { i = 4 } else { doOtherThings() } myFunc(i)
would produce an error saying that
ihasn't been initialized in theelsebranch.Taking the address of a variable is considered initialization (ex: in doing
json.Unmarshal(bytes, &config)config would be marked as initialized). -
explicitcastEnabled with
enable_explicitcast: truein.vinego.yaml.Checks that primitive literals are never implicitly casted (during assignments, function calls, and returns).
For example:
var x time.Duration x = 24
would produce an error saying that
24is being implicitly cast totime.Duration. -
capturederrEnabled with
enable_capturederr: truein.vinego.yaml.The
staticchecklinterSA4006check which makes sure we properly consume error variables ignores anything that happens with captured variables. Therefore if you accidentally captureerrfrom an outer function, assign it a value, then never check it,SA4006won't help you. Go's behavior using variable reuse/reinitialization with=and:=makes it easy to transplant code and accidentally reuse an existing variable, which makes it easy to accidentally capture external variables in closures.capturederrorwill give you an error when an error variable is captured by a closure (specifically error variables). As with the other linters here, capturing an error variable isn't necessarily incorrect, but it's generally unintended and when done unintentionally can lead to hard to track incorrect failure behavior.Example usage:
err := something() if err != nil { return err } wrapper(func() error { err = otherthing() }) ...
would produce an error saying that we're assgning to the captured variable
err. (The workaround is to doerr := otherthing(), which would make this error disappear and theSA4006one appear in its place, as intended.)
-
We provide a pre-made Docker batteries-included image for CI and development environments:
ghcr.io/upsun/vinego:latestIt includes
- Go
golangci-lintbuilt withvinegoincluded as amodule-type plugingcigoimportsdlvstaticcheck
You can build the Docker container yourself with
docker build --tag vinego srcat the root of this repo.Alternatively, you can build just the plugin
.so- see theDockerfilefor details (it's a straightforward Go.sobuild). -
Add custom linter plugin to your project's
.golangci.yamlfile:linters: enable: - vinego linters-settings: custom: vinego: type: "module" settings: enable_varinit: true enable_explicitcast: true enable_capturederr: true -
Run the linters with
docker run --rm --volume $PWD:/mnt --workdir /mnt vinego /bin/golangci-lint run --verbose. You should seevinegolisted in the output.
If you need an image with different tools or want to use the linter in some other situation, we recommend using golangci-lint's module system to bootstrap a new golangci-lint with the vinego linters included.
-
Install any recent version of golangci-lint
-
Create
.custom-gcl.ymlwith:version: v1.64.6 plugins: - module: 'github.com/upsun/vinego/src' import: 'github.com/upsun/vinego/src' version: latest
The top-level version is the version of golangci-lint that the process will bootstrap - it doesn't need to be the same version as the golangci-lint you installed in (1.).
-
Run
golangci-lint custom -vThis will produce a new
golangci-lint -
Use the new
golangci-lintwith this.golangci.yml:linters-settings: custom: vinego: type: "module" settings: enable_varinit: true enable_explicitcast: true enable_capturederr: true linters: enable: - vinego
Dependency updates and releases are automated:
- Dependabot opens weekly grouped pull requests for the Go modules
(
src/go.mod), theFROM golang:...base images (src/Dockerfile) and the pinned GitHub Actions. sync-pinscovers what Dependabot has no manager for: thegolangci-lintversion and thegodirective. It runs weekly, and again whenever a new base image lands onmain, raising its pull request the same way.- The
ciworkflow buildssrc/Dockerfileon the branch - building the customgolangci-lint, linting this repo with it and running the tests - and itspinsjob fails if the versions below have drifted apart. automergemerges the pull request only once ci has succeeded. A failing build leaves it open instead.- Anything landing on
mainthat changessrc/**cuts the next patch tag, andcipublishesghcr.io/upsun/vinegofor it.
To cut a larger version by hand, run the release workflow from the Actions tab
and choose minor or major.
| Pin | Where | Maintained by |
|---|---|---|
| Go modules | src/go.mod, src/go.sum |
Dependabot |
| Base images | src/Dockerfile (FROM golang:X.Y) |
Dependabot |
| Action versions | .github/workflows/*.yml |
Dependabot |
golangci-lint |
src/.custom-gcl.yml and src/Dockerfile |
sync-pins |
| Go language version | src/go.mod (the go directive) |
sync-pins |
The two golangci-lint values must always match each other, or
golangci-lint custom bootstraps a binary that disagrees with the plugin, and
the go directive must never ask for more than the base image provides. The
pins job enforces both on every push, so a half-updated set cannot be merged.