Skip to content

Commit 655e8ed

Browse files
authored
feat: add modernize analyzer suite (#6126)
1 parent 69217a5 commit 655e8ed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+3873
-0
lines changed

.golangci.next.reference.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ linters:
8585
- mirror
8686
- misspell
8787
- mnd
88+
- modernize
8889
- musttag
8990
- nakedret
9091
- nestif
@@ -199,6 +200,7 @@ linters:
199200
- mirror
200201
- misspell
201202
- mnd
203+
- modernize
202204
- musttag
203205
- nakedret
204206
- nestif
@@ -2106,6 +2108,45 @@ linters:
21062108
- '^math\.'
21072109
- '^http\.StatusText$'
21082110

2111+
modernize:
2112+
disable:
2113+
# Replace interface{} with any.
2114+
- any
2115+
# Replace for-range over b.N with b.Loop.
2116+
- bloop
2117+
# Replace []byte(fmt.Sprintf) with fmt.Appendf.
2118+
- fmtappendf
2119+
# Remove redundant re-declaration of loop variables.
2120+
- forvar
2121+
# Replace explicit loops over maps with calls to maps package.
2122+
- mapsloop
2123+
# Replace if/else statements with calls to min or max.
2124+
- minmax
2125+
# Simplify code by using go1.26's new(expr).
2126+
- newexpr
2127+
# Suggest replacing omitempty with omitzero for struct fields.
2128+
- omitzero
2129+
# Replace 3-clause for loops with for-range over integers.
2130+
- rangeint
2131+
# Replace reflect.TypeOf(x) with TypeFor[T]().
2132+
- reflecttypefor
2133+
# Replace loops with slices.Contains or slices.ContainsFunc.
2134+
- slicescontains
2135+
# Replace sort.Slice with slices.Sort for basic types.
2136+
- slicessort
2137+
# Use iterators instead of Len/At-style APIs.
2138+
- stditerators
2139+
# Replace HasPrefix/TrimPrefix with CutPrefix.
2140+
- stringscutprefix
2141+
# Replace ranging over Split/Fields with SplitSeq/FieldsSeq.
2142+
- stringsseq
2143+
# Replace += with strings.Builder.
2144+
- stringsbuilder
2145+
# Replace context.WithCancel with t.Context in tests.
2146+
- testingcontext
2147+
# Replace wg.Add(1)/go/wg.Done() with wg.Go.
2148+
- waitgroup
2149+
21092150
musttag:
21102151
# A set of custom functions to check in addition to the builtin ones.
21112152
# Default: json, xml, gopkg.in/yaml.v3, BurntSushi/toml, mitchellh/mapstructure, jmoiron/sqlx

jsonschema/golangci.next.jsonschema.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,28 @@
706706
"header"
707707
]
708708
},
709+
"modernize-analyzers": {
710+
"enum": [
711+
"any",
712+
"bloop",
713+
"fmtappendf",
714+
"forvar",
715+
"mapsloop",
716+
"minmax",
717+
"newexpr",
718+
"omitzero",
719+
"rangeint",
720+
"reflecttypefor",
721+
"slicescontains",
722+
"slicessort",
723+
"stditerators",
724+
"stringscutprefix",
725+
"stringsseq",
726+
"stringsbuilder",
727+
"testingcontext",
728+
"waitgroup"
729+
]
730+
},
709731
"wsl-checks": {
710732
"enum": [
711733
"assign",
@@ -835,6 +857,7 @@
835857
"mirror",
836858
"misspell",
837859
"mnd",
860+
"modernize",
838861
"musttag",
839862
"nakedret",
840863
"nestif",
@@ -2829,6 +2852,19 @@
28292852
}
28302853
}
28312854
},
2855+
"modernizeSettings": {
2856+
"type": "object",
2857+
"additionalProperties": false,
2858+
"properties": {
2859+
"disable": {
2860+
"description": "List of analyzers to disable.",
2861+
"type": "array",
2862+
"items": {
2863+
"$ref": "#/definitions/modernize-analyzers"
2864+
}
2865+
}
2866+
}
2867+
},
28322868
"nolintlintSettings": {
28332869
"type": "object",
28342870
"additionalProperties": false,
@@ -4747,6 +4783,9 @@
47474783
"mnd": {
47484784
"$ref": "#/definitions/settings/definitions/mndSettings"
47494785
},
4786+
"modernize": {
4787+
"$ref": "#/definitions/settings/definitions/modernizeSettings"
4788+
},
47504789
"nolintlint":{
47514790
"$ref": "#/definitions/settings/definitions/nolintlintSettings"
47524791
},

pkg/config/linters_settings.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ type LintersSettings struct {
269269
Makezero MakezeroSettings `mapstructure:"makezero"`
270270
Misspell MisspellSettings `mapstructure:"misspell"`
271271
Mnd MndSettings `mapstructure:"mnd"`
272+
Modernize ModernizeSettings `mapstructure:"modernize"`
272273
MustTag MustTagSettings `mapstructure:"musttag"`
273274
Nakedret NakedretSettings `mapstructure:"nakedret"`
274275
Nestif NestifSettings `mapstructure:"nestif"`
@@ -758,6 +759,10 @@ type MndSettings struct {
758759
IgnoredFunctions []string `mapstructure:"ignored-functions"`
759760
}
760761

762+
type ModernizeSettings struct {
763+
Disable []string `mapstructure:"disable"`
764+
}
765+
761766
type NoLintLintSettings struct {
762767
RequireExplanation bool `mapstructure:"require-explanation"`
763768
RequireSpecific bool `mapstructure:"require-specific"`
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package modernize
2+
3+
import (
4+
"slices"
5+
6+
"github.com/golangci/golangci-lint/v2/pkg/config"
7+
"github.com/golangci/golangci-lint/v2/pkg/goanalysis"
8+
9+
"golang.org/x/tools/go/analysis"
10+
"golang.org/x/tools/go/analysis/passes/modernize"
11+
)
12+
13+
func New(settings *config.ModernizeSettings) *goanalysis.Linter {
14+
var analyzers []*analysis.Analyzer
15+
16+
if settings == nil {
17+
analyzers = modernize.Suite
18+
} else {
19+
for _, analyzer := range modernize.Suite {
20+
if slices.Contains(settings.Disable, analyzer.Name) {
21+
continue
22+
}
23+
24+
analyzers = append(analyzers, analyzer)
25+
}
26+
}
27+
28+
return goanalysis.NewLinter(
29+
"modernize",
30+
"A suite of analyzers that suggest simplifications to Go code, using modern language and library features.",
31+
analyzers,
32+
nil).
33+
WithLoadMode(goanalysis.LoadModeTypesInfo)
34+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package modernize
2+
3+
import (
4+
"testing"
5+
6+
"github.com/golangci/golangci-lint/v2/test/testshared/integration"
7+
)
8+
9+
func TestFromTestdata(t *testing.T) {
10+
integration.RunTestdata(t)
11+
}
12+
13+
func TestFix(t *testing.T) {
14+
integration.RunFix(t)
15+
}
16+
17+
func TestFixPathPrefix(t *testing.T) {
18+
integration.RunFixPathPrefix(t)
19+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//golangcitest:args -Emodernize
2+
//golangcitest:expected_exitcode 0
3+
package any
4+
5+
func _(x interface{}) {} // want "interface{} can be replaced by any"
6+
7+
func _() {
8+
var x interface{} // want "interface{} can be replaced by any"
9+
const any = 1
10+
var y interface{} // nope: any is shadowed here
11+
_, _ = x, y
12+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//go:build go1.25
2+
3+
//golangcitest:args -Emodernize
4+
//golangcitest:expected_exitcode 0
5+
package bloop
6+
7+
import (
8+
"sync"
9+
"testing"
10+
)
11+
12+
func BenchmarkA(b *testing.B) {
13+
println("slow")
14+
b.ResetTimer()
15+
16+
for range b.N { // want "b.N can be modernized using b.Loop.."
17+
}
18+
}
19+
20+
func BenchmarkB(b *testing.B) {
21+
// setup
22+
{
23+
b.StopTimer()
24+
println("slow")
25+
b.StartTimer()
26+
}
27+
28+
for i := range b.N { // Nope. Should we change this to "for i := 0; b.Loop(); i++"?
29+
print(i)
30+
}
31+
32+
b.StopTimer()
33+
println("slow")
34+
}
35+
36+
func BenchmarkC(b *testing.B) {
37+
// setup
38+
{
39+
b.StopTimer()
40+
println("slow")
41+
b.StartTimer()
42+
}
43+
44+
for i := 0; i < b.N; i++ { // want "b.N can be modernized using b.Loop.."
45+
println("no uses of i")
46+
}
47+
48+
b.StopTimer()
49+
println("slow")
50+
}
51+
52+
func BenchmarkD(b *testing.B) {
53+
for i := 0; i < b.N; i++ { // want "b.N can be modernized using b.Loop.."
54+
println(i)
55+
}
56+
}
57+
58+
func BenchmarkE(b *testing.B) {
59+
b.Run("sub", func(b *testing.B) {
60+
b.StopTimer() // not deleted
61+
println("slow")
62+
b.StartTimer() // not deleted
63+
64+
// ...
65+
})
66+
b.ResetTimer()
67+
68+
for i := 0; i < b.N; i++ { // want "b.N can be modernized using b.Loop.."
69+
println("no uses of i")
70+
}
71+
72+
b.StopTimer()
73+
println("slow")
74+
}
75+
76+
func BenchmarkF(b *testing.B) {
77+
var wg sync.WaitGroup
78+
wg.Add(1)
79+
go func() {
80+
defer wg.Done()
81+
for i := 0; i < b.N; i++ { // nope: b.N accessed from a FuncLit
82+
}
83+
}()
84+
wg.Wait()
85+
}
86+
87+
func BenchmarkG(b *testing.B) {
88+
var wg sync.WaitGroup
89+
poster := func() {
90+
for i := 0; i < b.N; i++ { // nope: b.N accessed from a FuncLit
91+
}
92+
wg.Done()
93+
}
94+
wg.Add(2)
95+
for i := 0; i < 2; i++ {
96+
go poster()
97+
}
98+
wg.Wait()
99+
}
100+
101+
func BenchmarkH(b *testing.B) {
102+
var wg sync.WaitGroup
103+
wg.Add(1)
104+
go func() {
105+
defer wg.Done()
106+
for range b.N { // nope: b.N accessed from a FuncLit
107+
}
108+
}()
109+
wg.Wait()
110+
}
111+
112+
func BenchmarkI(b *testing.B) {
113+
for i := 0; i < b.N; i++ { // nope: b.N accessed more than once in benchmark
114+
}
115+
for i := 0; i < b.N; i++ { // nope: b.N accessed more than once in benchmark
116+
}
117+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//golangcitest:args -Emodernize
2+
//golangcitest:expected_exitcode 0
3+
package fieldsseq
4+
5+
import (
6+
"bytes"
7+
"strings"
8+
)
9+
10+
func _() {
11+
for _, line := range strings.Fields("") { // want "Ranging over FieldsSeq is more efficient"
12+
println(line)
13+
}
14+
for i, line := range strings.Fields("") { // nope: uses index var
15+
println(i, line)
16+
}
17+
for i, _ := range strings.Fields("") { // nope: uses index var
18+
println(i)
19+
}
20+
for i := range strings.Fields("") { // nope: uses index var
21+
println(i)
22+
}
23+
for _ = range strings.Fields("") { // want "Ranging over FieldsSeq is more efficient"
24+
}
25+
for range strings.Fields("") { // want "Ranging over FieldsSeq is more efficient"
26+
}
27+
for range bytes.Fields(nil) { // want "Ranging over FieldsSeq is more efficient"
28+
}
29+
{
30+
lines := strings.Fields("") // want "Ranging over FieldsSeq is more efficient"
31+
for _, line := range lines {
32+
println(line)
33+
}
34+
}
35+
{
36+
lines := strings.Fields("") // nope: lines is used not just by range
37+
for _, line := range lines {
38+
println(line)
39+
}
40+
println(lines)
41+
}
42+
}

0 commit comments

Comments
 (0)