Skip to content

oplint is for opinionated linter. It contains some rules that are highly opinionated and if you don't like that's ok.

License

Notifications You must be signed in to change notification settings

NSXBet/go-oplint

Repository files navigation

go-oplint

A Go linter for detecting optimization opportunities in concurrent code using the go/analysis framework.

Overview

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:

Rules

1. mapwithmutex

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
}

2. atomicwithmutex

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
}

Installation

go install github.com/NSXBet/go-oplint@latest

Or build from source:

git clone https://github.com/NSXBet/go-oplint.git
cd go-oplint
go build -o go-oplint ./cmd/go-oplint

Usage

Basic Usage

Check the current directory and all subdirectories:

go-oplint ./...

Check specific packages:

go-oplint ./pkg/... ./internal/...

Skipping Rules

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 ./...

Integration with go vet

You can use go-oplint as a go vet tool:

go vet -vettool=$(which go-oplint) ./...

Integration with golangci-lint

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.

The Automatic Way (Recommended)

  1. Create a .custom-gcl.yml file in your project root:
version: v2.6.2
plugins:
  - module: 'github.com/NSXBet/go-oplint'
    version: v1.0.0
  1. Run the command to build a custom golangci-lint binary:
golangci-lint custom
  1. 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
  1. 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.

The Manual Way

  1. Clone the golangci-lint repository:
git clone https://github.com/golangci/golangci-lint.git
cd golangci-lint
  1. Add a blank import to cmd/golangci-lint/plugins.go:
_ "github.com/NSXBet/go-oplint/plugin"
  1. Run go mod tidy to fetch the plugin:
go mod tidy
  1. Build your custom golangci-lint:
make build
  1. Configure the linters in your .golangci.yml (same as above)

  2. Use your custom golangci-lint binary

For more details, see the golangci-lint plugin documentation.

Help

go-oplint --help

Development

Project Structure

go-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

Running Tests

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 using analysistest
  • 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.sh

This script builds a custom golangci-lint binary and runs it against test files.

Adding New Rules

  1. 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},
}
  1. Add a flag to skip your rule in cmd/go-oplint/main.go
  2. Add your analyzer to the runLinter() function
  3. Create tests in internal/rules/yourrule_test.go
  4. Add test data in internal/rules/testdata/yourrule/

References

This linter is built using the go/analysis framework. For more information on writing Go analyzers, see:

License

See LICENSE file for details.

Contributing

Contributions are welcome! Please follow these guidelines:

Prerequisites

Before contributing, ensure you have:

Setting Up Development Environment

  1. Fork and clone the repository:
git clone https://github.com/YOUR_USERNAME/go-oplint.git
cd go-oplint
  1. Install dependencies:
go mod download
  1. Install the pre-commit hook (required):
make pre-commit-hook

This installs a git hook that automatically runs linting and tests before each commit to ensure code quality.

Development Workflow

  1. Create a new branch for your feature/fix:
git checkout -b feature/my-new-feature
  1. Make your changes

  2. Run tests locally:

make test
  1. Run linter:
make lint
  1. Or run both at once:
make pre-commit
  1. 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.

Code Quality Standards

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

Adding New Rules

To add a new linting rule:

  1. Create the analyzer in internal/rules/your_rule.go
  2. Add tests in internal/rules/your_rule_test.go
  3. Add test data in internal/rules/testdata/src/your_rule/
  4. Register the plugin in plugin/plugin.go
  5. Update CLI flags in cmd/go-oplint/main.go
  6. Update documentation in README.md and IMPLEMENTATION.md

See the existing rules (mapwithmutex, atomicwithmutex) as examples.

Pull Request Process

  1. Ensure all tests pass and linting is clean
  2. Update the README.md with details of changes if applicable
  3. Update the IMPLEMENTATION.md with technical details if needed
  4. The PR will be merged once you have the sign-off of maintainers

Questions?

Feel free to open an issue for:

  • Bug reports
  • Feature requests
  • Questions about the codebase
  • Discussion about potential improvements

About

oplint is for opinionated linter. It contains some rules that are highly opinionated and if you don't like that's ok.

Resources

License

Stars

Watchers

Forks

Packages

No packages published