From 787e46e3122791e9a06d154aa3ca0a88502bf53f Mon Sep 17 00:00:00 2001 From: dmunoz Date: Sat, 28 Jun 2025 12:38:17 +0200 Subject: [PATCH] feat: add gomodclean linter --- .golangci.next.reference.yml | 2 + go.mod | 1 + go.sum | 2 + jsonschema/golangci.next.jsonschema.json | 1 + pkg/golinters/gomodclean/gomodclean.go | 55 +++++++++++++++++++ .../gomodclean/gomodclean_integration_test.go | 11 ++++ pkg/golinters/gomodclean/testdata/go.mod | 7 +++ pkg/golinters/gomodclean/testdata/go.sum | 12 ++++ .../gomodclean/testdata/gomodclean.go | 15 +++++ pkg/lint/lintersdb/builder_linter.go | 5 ++ 10 files changed, 111 insertions(+) create mode 100644 pkg/golinters/gomodclean/gomodclean.go create mode 100644 pkg/golinters/gomodclean/gomodclean_integration_test.go create mode 100644 pkg/golinters/gomodclean/testdata/go.mod create mode 100644 pkg/golinters/gomodclean/testdata/go.sum create mode 100644 pkg/golinters/gomodclean/testdata/gomodclean.go diff --git a/.golangci.next.reference.yml b/.golangci.next.reference.yml index 66699d365084..72bee332acb8 100644 --- a/.golangci.next.reference.yml +++ b/.golangci.next.reference.yml @@ -62,6 +62,7 @@ linters: - godot - godox - goheader + - gomodclean - gomoddirectives - gomodguard - goprintffuncname @@ -172,6 +173,7 @@ linters: - godot - godox - goheader + - gomodclean - gomoddirectives - gomodguard - goprintffuncname diff --git a/go.mod b/go.mod index ef62abf3ad54..8f7a66bd6628 100644 --- a/go.mod +++ b/go.mod @@ -37,6 +37,7 @@ require ( github.com/curioswitch/go-reassign v0.3.0 github.com/daixiang0/gci v0.13.6 github.com/denis-tingaikin/go-header v0.5.0 + github.com/dmrioja/gomodclean v0.2.0 github.com/fatih/color v1.18.0 github.com/firefart/nonamedreturns v1.0.6 github.com/fzipp/gocyclo v0.6.0 diff --git a/go.sum b/go.sum index b92fdfee4b85..f1e0d6699f27 100644 --- a/go.sum +++ b/go.sum @@ -152,6 +152,8 @@ github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42 github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dmrioja/gomodclean v0.2.0 h1:mI8lkKS+F4bfdjPSZ02tHSCTd+SbN01q2Um14ub+z3I= +github.com/dmrioja/gomodclean v0.2.0/go.mod h1:j6STxrv3Rt7NVbNBLSvf/H5qA7+qO7AwtfhSPG0djCo= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= diff --git a/jsonschema/golangci.next.jsonschema.json b/jsonschema/golangci.next.jsonschema.json index 31e7fc2ffa7a..b7d9fdc2a9e5 100644 --- a/jsonschema/golangci.next.jsonschema.json +++ b/jsonschema/golangci.next.jsonschema.json @@ -764,6 +764,7 @@ "godox", "err113", "goheader", + "gomodclean", "gomoddirectives", "gomodguard", "goprintffuncname", diff --git a/pkg/golinters/gomodclean/gomodclean.go b/pkg/golinters/gomodclean/gomodclean.go new file mode 100644 index 000000000000..3cd751b3c60e --- /dev/null +++ b/pkg/golinters/gomodclean/gomodclean.go @@ -0,0 +1,55 @@ +package gomodclean + +import ( + "sync" + + gomodcleananalyzer "github.com/dmrioja/gomodclean/pkg/analyzer" + + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/v2/pkg/goanalysis" + "github.com/golangci/golangci-lint/v2/pkg/lint/linter" + "github.com/golangci/golangci-lint/v2/pkg/result" +) + +const linterName = "gomodclean" + +func New() *goanalysis.Linter { + var issues []*goanalysis.Issue + var once sync.Once + + analyzer := &analysis.Analyzer{ + Name: linterName, + Doc: "Linter to check dependencies are well structured inside your go.mod file.", + Run: goanalysis.DummyRun, + } + + return goanalysis. + NewLinterFromAnalyzer(analyzer). + WithContextSetter(func(lintCtx *linter.Context) { + analyzer.Run = func(pass *analysis.Pass) (any, error) { + once.Do(func() { + results, err := gomodcleananalyzer.Analyze() + if err != nil { + lintCtx.Log.Warnf("running %s failed: %s: "+ + "if you are not using go modules it is suggested to disable this linter", linterName, err) + return + } + + for _, p := range results { + issues = append(issues, goanalysis.NewIssue(&result.Issue{ + FromLinter: linterName, + Pos: p.Position, + Text: p.Text, + }, pass)) + } + }) + + return nil, nil + } + }). + WithIssuesReporter(func(*linter.Context) []*goanalysis.Issue { + return issues + }). + WithLoadMode(goanalysis.LoadModeSyntax) +} diff --git a/pkg/golinters/gomodclean/gomodclean_integration_test.go b/pkg/golinters/gomodclean/gomodclean_integration_test.go new file mode 100644 index 000000000000..a956f36c012d --- /dev/null +++ b/pkg/golinters/gomodclean/gomodclean_integration_test.go @@ -0,0 +1,11 @@ +package gomodclean + +import ( + "testing" + + "github.com/golangci/golangci-lint/v2/test/testshared/integration" +) + +func TestFromTestdata(t *testing.T) { + integration.RunTestdata(t) +} diff --git a/pkg/golinters/gomodclean/testdata/go.mod b/pkg/golinters/gomodclean/testdata/go.mod new file mode 100644 index 000000000000..11213064f3b7 --- /dev/null +++ b/pkg/golinters/gomodclean/testdata/go.mod @@ -0,0 +1,7 @@ +module gomodclean + +go 1.24.2 + +require github.com/dmrioja/gomodclean v0.1.1-0.20250628101813-52ee7046c81a + +require golang.org/x/mod v0.25.0 // indirect diff --git a/pkg/golinters/gomodclean/testdata/go.sum b/pkg/golinters/gomodclean/testdata/go.sum new file mode 100644 index 000000000000..8d1feb3b9c97 --- /dev/null +++ b/pkg/golinters/gomodclean/testdata/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dmrioja/gomodclean v0.1.1-0.20250628101813-52ee7046c81a h1:JM2cjcmGKlu5D2e6pdLkX5yrcYj9PMhrFHYVlzhBfFg= +github.com/dmrioja/gomodclean v0.1.1-0.20250628101813-52ee7046c81a/go.mod h1:j6STxrv3Rt7NVbNBLSvf/H5qA7+qO7AwtfhSPG0djCo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/golinters/gomodclean/testdata/gomodclean.go b/pkg/golinters/gomodclean/testdata/gomodclean.go new file mode 100644 index 000000000000..80f60ba83dbe --- /dev/null +++ b/pkg/golinters/gomodclean/testdata/gomodclean.go @@ -0,0 +1,15 @@ +//golangcitest:args -Egomodclean +package testdata + +import ( + "log" + + "github.com/dmrioja/gomodclean/pkg/analyzer" +) + +// correctGoModFile imports some dependencies to build the tesdata go.mod file. +func correctGoModFile() { //nolint:unused + results, err := analyzer.Analyze() + log.Println(results) + log.Println(err) +} diff --git a/pkg/lint/lintersdb/builder_linter.go b/pkg/lint/lintersdb/builder_linter.go index 734aab63cd1c..1db2c9b9cc4e 100644 --- a/pkg/lint/lintersdb/builder_linter.go +++ b/pkg/lint/lintersdb/builder_linter.go @@ -50,6 +50,7 @@ import ( "github.com/golangci/golangci-lint/v2/pkg/golinters/goheader" "github.com/golangci/golangci-lint/v2/pkg/golinters/goimports" "github.com/golangci/golangci-lint/v2/pkg/golinters/golines" + "github.com/golangci/golangci-lint/v2/pkg/golinters/gomodclean" "github.com/golangci/golangci-lint/v2/pkg/golinters/gomoddirectives" "github.com/golangci/golangci-lint/v2/pkg/golinters/gomodguard" "github.com/golangci/golangci-lint/v2/pkg/golinters/goprintffuncname" @@ -375,6 +376,10 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithSince("v1.22.0"). WithURL("https://github.com/tommy-muehle/go-mnd"), + linter.NewConfig(gomodclean.New()). + WithSince("v2.2.0"). + WithURL("https://github.com/dmrioja/gomodclean"), + linter.NewConfig(gomoddirectives.New(&cfg.Linters.Settings.GoModDirectives)). WithSince("v1.39.0"). WithURL("https://github.com/ldez/gomoddirectives"),