A Go linter for detecting optimization opportunities in concurrent code using the go/analysis framework.
go-oplint identifies patterns in Go code that can be optimized using better concurrency primitives. Built with the Go analysis framework and designed to integrate seamlessly with go vet and other Go tooling. It currently implements two rules:
Detects structs that contain both a map and a mutex (sync.Mutex or sync.RWMutex) and suggests using xsync/v4 Map (https://github.com/puzpuzpuz/xsync) instead for better performance.
Example:
// Bad - will be flagged
type Cache struct {
mu sync.Mutex
data map[string]string
}
// Good - use xsync instead
type Cache struct {
data *xsync.Map[string]string
}Detects structs that contain atomic-compatible types (int32, int64, uint32, uint64, uintptr) along with a mutex and suggests using the sync/atomic package instead.
Example:
// Bad - will be flagged
type Counter struct {
mu sync.Mutex
count int64
}
// Good - use atomic instead
type Counter struct {
count atomic.Int64
}go install github.com/NSXBet/go-oplint@latestOr build from source:
git clone https://github.com/NSXBet/go-oplint.git
cd go-oplint
go build -o go-oplint ./cmd/go-oplintCheck the current directory and all subdirectories:
go-oplint ./...Check specific packages:
go-oplint ./pkg/... ./internal/...You can skip specific rules using flags:
# Skip the mapwithmutex rule
go-oplint --skip-mapwithmutex ./...
# Skip the atomicwithmutex rule
go-oplint --skip-atomicwithmutex ./...
# Skip all rules (no analysis will be performed)
go-oplint --skip-mapwithmutex --skip-atomicwithmutex ./...You can use go-oplint as a go vet tool:
go vet -vettool=$(which go-oplint) ./...You can integrate go-oplint into golangci-lint using the module plugin system. This allows you to run go-oplint alongside other linters in your CI/CD pipeline.
- Create a
.custom-gcl.ymlfile in your project root:
version: v2.6.2
plugins:
- module: 'github.com/NSXBet/go-oplint'
version: v1.0.0- Run the command to build a custom golangci-lint binary:
golangci-lint custom- Configure the linters in your
.golangci.yml:
version: "2"
linters:
enable:
- mapwithmutex
- atomicwithmutex
settings:
custom:
mapwithmutex:
type: "module"
description: Detects structs with both a map and a mutex, suggests xsync/v4 Map.
settings:
skip-mapwithmutex: false # Set to true to disable this rule
atomicwithmutex:
type: "module"
description: Detects structs with atomic-compatible types and mutex, suggests atomic package.
settings:
skip-atomicwithmutex: false # Set to true to disable this rule- Run your custom golangci-lint binary:
./custom-gcl run ./...Note: When using go-oplint through golangci-lint, you can disable specific rules using the settings configuration in .golangci.yml (as shown above), rather than using command-line flags.
- Clone the golangci-lint repository:
git clone https://github.com/golangci/golangci-lint.git
cd golangci-lint- Add a blank import to
cmd/golangci-lint/plugins.go:
_ "github.com/NSXBet/go-oplint/plugin"- Run
go mod tidyto fetch the plugin:
go mod tidy- Build your custom golangci-lint:
make build-
Configure the linters in your
.golangci.yml(same as above) -
Use your custom golangci-lint binary
For more details, see the golangci-lint plugin documentation.
go-oplint --helpgo-oplint/
├── cmd/
│ └── root.go # Cobra root command
├── main.go # CLI entry point
├── internal/
│ └── rules/ # Linting rules
│ ├── mapwithmutex.go
│ ├── mapwithmutex_test.go
│ ├── atomicwithmutex.go
│ ├── atomicwithmutex_test.go
│ └── testdata/ # Test fixtures
│ ├── mapwithmutex/
│ └── atomicwithmutex/
├── go.mod
├── go.sum
├── LICENSE
└── README.md
Run all tests (unit + integration):
go test ./...Run tests with verbose output:
go test -v ./...The test suite includes:
- Unit tests (
internal/rules/*_test.go): Test individual analyzers usinganalysistest - Plugin tests (
plugin/plugin_test.go): Test plugin registration and configuration - Integration tests (
plugin/integration_test.go): Test plugin behavior with real Go packages - Main integration test (
integration_test.go): Test overall plugin loading
For manual integration testing with golangci-lint, you can use:
./test-integration.shThis script builds a custom golangci-lint binary and runs it against test files.
- Create a new analyzer in
internal/rules/:
var YourAnalyzer = &analysis.Analyzer{
Name: "yourrule",
Doc: "description of your rule",
Run: runYourAnalyzer,
Requires: []*analysis.Analyzer{inspect.Analyzer},
}- Add a flag to skip your rule in
cmd/go-oplint/main.go - Add your analyzer to the
runLinter()function - Create tests in
internal/rules/yourrule_test.go - Add test data in
internal/rules/testdata/yourrule/
This linter is built using the go/analysis framework. For more information on writing Go analyzers, see:
See LICENSE file for details.
Contributions are welcome! Please follow these guidelines:
Before contributing, ensure you have:
- Go 1.22.0 or later installed
- golangci-lint installed (installation guide)
- Fork and clone the repository:
git clone https://github.com/YOUR_USERNAME/go-oplint.git
cd go-oplint- Install dependencies:
go mod download- Install the pre-commit hook (required):
make pre-commit-hookThis installs a git hook that automatically runs linting and tests before each commit to ensure code quality.
- Create a new branch for your feature/fix:
git checkout -b feature/my-new-feature-
Make your changes
-
Run tests locally:
make test- Run linter:
make lint- Or run both at once:
make pre-commit- Commit your changes (pre-commit hook will run automatically):
git commit -m "Add my new feature"If the pre-commit hook fails, fix the issues and try again. Do not use --no-verify to skip the hook.
All contributions must:
- ✅ Pass all existing tests
- ✅ Pass golangci-lint checks
- ✅ Include tests for new functionality
- ✅ Update documentation as needed
- ✅ Follow Go best practices and idioms
To add a new linting rule:
- Create the analyzer in
internal/rules/your_rule.go - Add tests in
internal/rules/your_rule_test.go - Add test data in
internal/rules/testdata/src/your_rule/ - Register the plugin in
plugin/plugin.go - Update CLI flags in
cmd/go-oplint/main.go - Update documentation in README.md and IMPLEMENTATION.md
See the existing rules (mapwithmutex, atomicwithmutex) as examples.
- Ensure all tests pass and linting is clean
- Update the README.md with details of changes if applicable
- Update the IMPLEMENTATION.md with technical details if needed
- The PR will be merged once you have the sign-off of maintainers
Feel free to open an issue for:
- Bug reports
- Feature requests
- Questions about the codebase
- Discussion about potential improvements