Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .golangci.next.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ linters:
- mirror
- misspell
- mnd
- modernize
- musttag
- nakedret
- nestif
Expand Down Expand Up @@ -199,6 +200,7 @@ linters:
- mirror
- misspell
- mnd
- modernize
- musttag
- nakedret
- nestif
Expand Down Expand Up @@ -2106,6 +2108,45 @@ linters:
- '^math\.'
- '^http\.StatusText$'

modernize:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great to finally see this in here! 🚀 What are the default values for this linter?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disable:
# Replace interface{} with any.
- any
# Replace for-range over b.N with b.Loop.
- bloop
# Replace []byte(fmt.Sprintf) with fmt.Appendf.
- fmtappendf
# Remove redundant re-declaration of loop variables.
- forvar
# Replace explicit loops over maps with calls to maps package.
- mapsloop
# Replace if/else statements with calls to min or max.
- minmax
# Simplify code by using go1.26's new(expr).
- newexpr
# Suggest replacing omitempty with omitzero for struct fields.
- omitzero
# Replace 3-clause for loops with for-range over integers.
- rangeint
# Replace reflect.TypeOf(x) with TypeFor[T]().
- reflecttypefor
# Replace loops with slices.Contains or slices.ContainsFunc.
- slicescontains
# Replace sort.Slice with slices.Sort for basic types.
- slicessort
# Use iterators instead of Len/At-style APIs.
- stditerators
# Replace HasPrefix/TrimPrefix with CutPrefix.
- stringscutprefix
# Replace ranging over Split/Fields with SplitSeq/FieldsSeq.
- stringsseq
# Replace += with strings.Builder.
- stringsbuilder
# Replace context.WithCancel with t.Context in tests.
- testingcontext
# Replace wg.Add(1)/go/wg.Done() with wg.Go.
- waitgroup

musttag:
# A set of custom functions to check in addition to the builtin ones.
# Default: json, xml, gopkg.in/yaml.v3, BurntSushi/toml, mitchellh/mapstructure, jmoiron/sqlx
Expand Down
39 changes: 39 additions & 0 deletions jsonschema/golangci.next.jsonschema.json
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,28 @@
"header"
]
},
"modernize-analyzers": {
"enum": [
"any",
"bloop",
"fmtappendf",
"forvar",
"mapsloop",
"minmax",
"newexpr",
"omitzero",
"rangeint",
"reflecttypefor",
"slicescontains",
"slicessort",
"stditerators",
"stringscutprefix",
"stringsseq",
"stringsbuilder",
"testingcontext",
"waitgroup"
]
},
"wsl-checks": {
"enum": [
"assign",
Expand Down Expand Up @@ -835,6 +857,7 @@
"mirror",
"misspell",
"mnd",
"modernize",
"musttag",
"nakedret",
"nestif",
Expand Down Expand Up @@ -2829,6 +2852,19 @@
}
}
},
"modernizeSettings": {
"type": "object",
"additionalProperties": false,
"properties": {
"disable": {
"description": "List of analyzers to disable.",
"type": "array",
"items": {
"$ref": "#/definitions/modernize-analyzers"
}
}
}
},
"nolintlintSettings": {
"type": "object",
"additionalProperties": false,
Expand Down Expand Up @@ -4747,6 +4783,9 @@
"mnd": {
"$ref": "#/definitions/settings/definitions/mndSettings"
},
"modernize": {
"$ref": "#/definitions/settings/definitions/modernizeSettings"
},
"nolintlint":{
"$ref": "#/definitions/settings/definitions/nolintlintSettings"
},
Expand Down
5 changes: 5 additions & 0 deletions pkg/config/linters_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ type LintersSettings struct {
Makezero MakezeroSettings `mapstructure:"makezero"`
Misspell MisspellSettings `mapstructure:"misspell"`
Mnd MndSettings `mapstructure:"mnd"`
Modernize ModernizeSettings `mapstructure:"modernize"`
MustTag MustTagSettings `mapstructure:"musttag"`
Nakedret NakedretSettings `mapstructure:"nakedret"`
Nestif NestifSettings `mapstructure:"nestif"`
Expand Down Expand Up @@ -758,6 +759,10 @@ type MndSettings struct {
IgnoredFunctions []string `mapstructure:"ignored-functions"`
}

type ModernizeSettings struct {
Disable []string `mapstructure:"disable"`
}

type NoLintLintSettings struct {
RequireExplanation bool `mapstructure:"require-explanation"`
RequireSpecific bool `mapstructure:"require-specific"`
Expand Down
34 changes: 34 additions & 0 deletions pkg/golinters/modernize/modernize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package modernize

import (
"slices"

"github.com/golangci/golangci-lint/v2/pkg/config"
"github.com/golangci/golangci-lint/v2/pkg/goanalysis"

"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/modernize"
)

func New(settings *config.ModernizeSettings) *goanalysis.Linter {
var analyzers []*analysis.Analyzer

if settings == nil {
analyzers = modernize.Suite
} else {
for _, analyzer := range modernize.Suite {
if slices.Contains(settings.Disable, analyzer.Name) {
continue
}

analyzers = append(analyzers, analyzer)
}
}

return goanalysis.NewLinter(
"modernize",
"A suite of analyzers that suggest simplifications to Go code, using modern language and library features.",
analyzers,
nil).
WithLoadMode(goanalysis.LoadModeTypesInfo)
}
19 changes: 19 additions & 0 deletions pkg/golinters/modernize/modernize_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package modernize

import (
"testing"

"github.com/golangci/golangci-lint/v2/test/testshared/integration"
)

func TestFromTestdata(t *testing.T) {
integration.RunTestdata(t)
}

func TestFix(t *testing.T) {
integration.RunFix(t)
}

func TestFixPathPrefix(t *testing.T) {
integration.RunFixPathPrefix(t)
}
12 changes: 12 additions & 0 deletions pkg/golinters/modernize/testdata/fix/in/any.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//golangcitest:args -Emodernize
//golangcitest:expected_exitcode 0
package any

func _(x interface{}) {} // want "interface{} can be replaced by any"

func _() {
var x interface{} // want "interface{} can be replaced by any"
const any = 1
var y interface{} // nope: any is shadowed here
_, _ = x, y
}
117 changes: 117 additions & 0 deletions pkg/golinters/modernize/testdata/fix/in/bloop_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//go:build go1.25

//golangcitest:args -Emodernize
//golangcitest:expected_exitcode 0
package bloop

import (
"sync"
"testing"
)

func BenchmarkA(b *testing.B) {
println("slow")
b.ResetTimer()

for range b.N { // want "b.N can be modernized using b.Loop.."
}
}

func BenchmarkB(b *testing.B) {
// setup
{
b.StopTimer()
println("slow")
b.StartTimer()
}

for i := range b.N { // Nope. Should we change this to "for i := 0; b.Loop(); i++"?
print(i)
}

b.StopTimer()
println("slow")
}

func BenchmarkC(b *testing.B) {
// setup
{
b.StopTimer()
println("slow")
b.StartTimer()
}

for i := 0; i < b.N; i++ { // want "b.N can be modernized using b.Loop.."
println("no uses of i")
}

b.StopTimer()
println("slow")
}

func BenchmarkD(b *testing.B) {
for i := 0; i < b.N; i++ { // want "b.N can be modernized using b.Loop.."
println(i)
}
}

func BenchmarkE(b *testing.B) {
b.Run("sub", func(b *testing.B) {
b.StopTimer() // not deleted
println("slow")
b.StartTimer() // not deleted

// ...
})
b.ResetTimer()

for i := 0; i < b.N; i++ { // want "b.N can be modernized using b.Loop.."
println("no uses of i")
}

b.StopTimer()
println("slow")
}

func BenchmarkF(b *testing.B) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < b.N; i++ { // nope: b.N accessed from a FuncLit
}
}()
wg.Wait()
}

func BenchmarkG(b *testing.B) {
var wg sync.WaitGroup
poster := func() {
for i := 0; i < b.N; i++ { // nope: b.N accessed from a FuncLit
}
wg.Done()
}
wg.Add(2)
for i := 0; i < 2; i++ {
go poster()
}
wg.Wait()
}

func BenchmarkH(b *testing.B) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for range b.N { // nope: b.N accessed from a FuncLit
}
}()
wg.Wait()
}

func BenchmarkI(b *testing.B) {
for i := 0; i < b.N; i++ { // nope: b.N accessed more than once in benchmark
}
for i := 0; i < b.N; i++ { // nope: b.N accessed more than once in benchmark
}
}
42 changes: 42 additions & 0 deletions pkg/golinters/modernize/testdata/fix/in/fieldsseq.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//golangcitest:args -Emodernize
//golangcitest:expected_exitcode 0
package fieldsseq

import (
"bytes"
"strings"
)

func _() {
for _, line := range strings.Fields("") { // want "Ranging over FieldsSeq is more efficient"
println(line)
}
for i, line := range strings.Fields("") { // nope: uses index var
println(i, line)
}
for i, _ := range strings.Fields("") { // nope: uses index var
println(i)
}
for i := range strings.Fields("") { // nope: uses index var
println(i)
}
for _ = range strings.Fields("") { // want "Ranging over FieldsSeq is more efficient"
}
for range strings.Fields("") { // want "Ranging over FieldsSeq is more efficient"
}
for range bytes.Fields(nil) { // want "Ranging over FieldsSeq is more efficient"
}
{
lines := strings.Fields("") // want "Ranging over FieldsSeq is more efficient"
for _, line := range lines {
println(line)
}
}
{
lines := strings.Fields("") // nope: lines is used not just by range
for _, line := range lines {
println(line)
}
println(lines)
}
}
Loading
Loading