Custom Go linters used on Wormhole CI. Each linter is a golangci-lint
module plugin and lives
as its own Go module under rules/<linter>/.
Prefer to use the release builds.
Currently supported linters:
| Name | Purpose | Features |
|---|---|---|
channelcheck |
Flag channel usage patterns that can deadlock or block. | • Blocking channel sends outside a select• Unbuffered channel creation ( make(chan T))• Empty default: that silently drops a send (advisory, off by default)• Ignore specific channels |
make build # bin/wormhole-lint
make build-golangci-lint # bin/wormhole-golangci-lint
make test # root + each rules/<linter> module
make build-golangci-lint first installs the pinned upstream
golangci-lint into bin/ (per GOLANGCI_LINT_HASH), then runs
golangci-lint custom.
Standalone:
bin/wormhole-lint ./...
Via the custom golangci-lint:
bin/wormhole-golangci-lint run --timeout=10m ./...
Module plugins are addressed under linters.settings.custom.<name> and
enabled in linters.enable by the plugin's registered name. Example for
channelcheck:
version: "2"
linters:
enable:
- channelcheck
settings:
custom:
channelcheck:
type: module
description: reports channel blocking issues
settings:
blocking: true
unbuffered: false
bufferMax: 0
emptyDefault: false
ignoreChannelsByName: [errC]channelcheck settings:
| Setting | Type | Default | Description |
|---|---|---|---|
blocking |
bool |
true |
Flag blocking sends that have no escape (timer, default:, or ctx.Done()), including sends outside any select. |
unbuffered |
bool |
false |
Flag unbuffered channel creation (make(chan T) / make(chan T, 0)). |
bufferMax |
uint |
0 |
Flag channel buffers larger than this. 0 disables the check. |
emptyDefault |
bool |
false |
Advisory: flag an empty default: in a select with a send, since it silently drops the send. Off by default — it fires on the idiomatic non-blocking/coalescing-send pattern, so enable it only where dropping should always be logged or documented. |
ignoreChannelsByName |
[]string |
[] |
Channel/field names whose direct sends are exempt from the blocking-send, empty-default, and ctx.Done() checks. |
- Scaffold the module:
mkdir -p rules/<linter> && cd rules/<linter> go mod init github.com/wormhole-foundation/wormhole/linters/rules/<linter> - Implement
<linter>.gofollowing the channelcheck reference (rules/channelcheck/channelcheck.go):- Export an
Analyzerof type*analysis.Analyzer. - Define a
Settingsstruct and aNew(any) (register.LinterPlugin, error)constructor that decodes settings viaregister.DecodeSettings[Settings]. - Implement
BuildAnalyzers()andGetLoadMode()on your plugin type. - In
init(), callregister.Plugin("<name>", New)sogolangci-lint custompicks it up.
- Export an
- Add tests + fixtures under
rules/<linter>/testdata/followingrules/channelcheck/channelcheck_test.go. - Wire it into the root module so the aggregator can import it:
- In root
go.mod, addrequire github.com/wormhole-foundation/wormhole/linters/rules/<linter> v0.0.0andreplace github.com/wormhole-foundation/wormhole/linters/rules/<linter> => ./rules/<linter>. - In
cmd/wormhole-lint/main.go, add the import and append<linter>.Analyzerto themultichecker.Maincall.
- In root
- Wire it into
.custom-gcl.ymlso the custom golangci-lint picks it up:plugins: - module: github.com/wormhole-foundation/wormhole/linters/rules/<linter> path: ./rules/<linter>
make test && make build && make build-golangci-lintto verify.- Add linter to
.golangci.yml. - Fix linter errors in the monorepo with legitimate changes or a
nolintcomment.
.custom-gcl.yml # plugin manifest for `golangci-lint custom`
Makefile
go.mod # root module: cmd/* + replaces for each rules/<linter>
cmd/
wormhole-lint/ # multichecker aggregator binary
rules/
channelcheck/ # standalone Go module per linter
channelcheck.go
channelcheck_test.go
go.mod
testdata/