Skip to content

Commit 7435a81

Browse files
adonovangopherbot
authored andcommitted
gopls/internal/analysis/modernize: document workflow
Also, add and document a -category flag on the modernize analyzer. (This is a stopgap until the checker driver supports this flag generally; see issue 72008 and CL 655555.) Fixes golang/go#72008 Change-Id: Iad5bc277b1251f2edb935f16077fd3add61041c5 Reviewed-on: https://go-review.googlesource.com/c/tools/+/655436 Auto-Submit: Alan Donovan <[email protected]> Commit-Queue: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent b08c7a2 commit 7435a81

File tree

4 files changed

+166
-58
lines changed

4 files changed

+166
-58
lines changed

gopls/doc/analyzers.md

+65-24
Original file line numberDiff line numberDiff line change
@@ -476,39 +476,80 @@ Package documentation: [lostcancel](https://pkg.go.dev/golang.org/x/tools/go/ana
476476

477477

478478
This analyzer reports opportunities for simplifying and clarifying
479-
existing code by using more modern features of Go, such as:
480-
481-
- replacing an if/else conditional assignment by a call to the
482-
built-in min or max functions added in go1.21;
483-
- replacing sort.Slice(x, func(i, j int) bool) { return s[i] < s[j] }
484-
by a call to slices.Sort(s), added in go1.21;
485-
- replacing interface{} by the 'any' type added in go1.18;
486-
- replacing append([]T(nil), s...) by slices.Clone(s) or
487-
slices.Concat(s), added in go1.21;
488-
- replacing a loop around an m[k]=v map update by a call
489-
to one of the Collect, Copy, Clone, or Insert functions
490-
from the maps package, added in go1.21;
491-
- replacing []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...),
492-
added in go1.19;
493-
- replacing uses of context.WithCancel in tests with t.Context, added in
494-
go1.24;
495-
- replacing omitempty by omitzero on structs, added in go1.24;
496-
- replacing append(s[:i], s[i+1]...) by slices.Delete(s, i, i+1),
497-
added in go1.21
498-
- replacing a 3-clause for i := 0; i < n; i++ {} loop by
499-
for i := range n {}, added in go1.22;
500-
- replacing Split in "for range strings.Split(...)" by go1.24's
501-
more efficient SplitSeq, or Fields with FieldSeq;
479+
existing code by using more modern features of Go and its standard
480+
library.
481+
482+
Each diagnostic provides a fix. Our intent is that these fixes may
483+
be safely applied en masse without changing the behavior of your
484+
program. In some cases the suggested fixes are imperfect and may
485+
lead to (for example) unused imports or unused local variables,
486+
causing build breakage. However, these problems are generally
487+
trivial to fix. We regard any modernizer whose fix changes program
488+
behavior to have a serious bug and will endeavor to fix it.
502489

503490
To apply all modernization fixes en masse, you can use the
504491
following command:
505492

506-
$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./...
493+
$ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
507494

508495
If the tool warns of conflicting fixes, you may need to run it more
509496
than once until it has applied all fixes cleanly. This command is
510497
not an officially supported interface and may change in the future.
511498

499+
Changes produced by this tool should be reviewed as usual before
500+
being merged. In some cases, a loop may be replaced by a simple
501+
function call, causing comments within the loop to be discarded.
502+
Human judgment may be required to avoid losing comments of value.
503+
504+
Each diagnostic reported by modernize has a specific category. (The
505+
categories are listed below.) Diagnostics in some categories, such
506+
as "efaceany" (which replaces "interface{}" with "any" where it is
507+
safe to do so) are particularly numerous. It may ease the burden of
508+
code review to apply fixes in two passes, the first change
509+
consisting only of fixes of category "efaceany", the second
510+
consisting of all others. This can be achieved using the -category flag:
511+
512+
$ modernize -category=efaceany -fix -test ./...
513+
$ modernize -category=-efaceany -fix -test ./...
514+
515+
Categories of modernize diagnostic:
516+
517+
- minmax: replace an if/else conditional assignment by a call to
518+
the built-in min or max functions added in go1.21.
519+
520+
- sortslice: replace sort.Slice(x, func(i, j int) bool) { return s[i] < s[j] }
521+
by a call to slices.Sort(s), added in go1.21.
522+
523+
- efaceany: replace interface{} by the 'any' type added in go1.18.
524+
525+
- slicesclone: replace append([]T(nil), s...) by slices.Clone(s) or
526+
slices.Concat(s), added in go1.21.
527+
528+
- mapsloop: replace a loop around an m[k]=v map update by a call
529+
to one of the Collect, Copy, Clone, or Insert functions from
530+
the maps package, added in go1.21.
531+
532+
- fmtappendf: replace []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...),
533+
added in go1.19.
534+
535+
- testingcontext: replace uses of context.WithCancel in tests
536+
with t.Context, added in go1.24.
537+
538+
- omitzero: replace omitempty by omitzero on structs, added in go1.24.
539+
540+
- bloop: replace "for i := range b.N" or "for range b.N" in a
541+
benchmark with "for b.Loop()", and remove any preceding calls
542+
to b.StopTimer, b.StartTimer, and b.ResetTimer.
543+
544+
- slicesdelete: replace append(s[:i], s[i+1]...) by
545+
slices.Delete(s, i, i+1), added in go1.21.
546+
547+
- rangeint: replace a 3-clause "for i := 0; i < n; i++" loop by
548+
"for i := range n", added in go1.22.
549+
550+
- stringseq: replace Split in "for range strings.Split(...)" by go1.24's
551+
more efficient SplitSeq, or Fields with FieldSeq.
552+
512553
Default: on.
513554

514555
Package documentation: [modernize](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize)

gopls/internal/analysis/modernize/doc.go

+65-24
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,77 @@
99
// modernize: simplify code by using modern constructs
1010
//
1111
// This analyzer reports opportunities for simplifying and clarifying
12-
// existing code by using more modern features of Go, such as:
13-
//
14-
// - replacing an if/else conditional assignment by a call to the
15-
// built-in min or max functions added in go1.21;
16-
// - replacing sort.Slice(x, func(i, j int) bool) { return s[i] < s[j] }
17-
// by a call to slices.Sort(s), added in go1.21;
18-
// - replacing interface{} by the 'any' type added in go1.18;
19-
// - replacing append([]T(nil), s...) by slices.Clone(s) or
20-
// slices.Concat(s), added in go1.21;
21-
// - replacing a loop around an m[k]=v map update by a call
22-
// to one of the Collect, Copy, Clone, or Insert functions
23-
// from the maps package, added in go1.21;
24-
// - replacing []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...),
25-
// added in go1.19;
26-
// - replacing uses of context.WithCancel in tests with t.Context, added in
27-
// go1.24;
28-
// - replacing omitempty by omitzero on structs, added in go1.24;
29-
// - replacing append(s[:i], s[i+1]...) by slices.Delete(s, i, i+1),
30-
// added in go1.21
31-
// - replacing a 3-clause for i := 0; i < n; i++ {} loop by
32-
// for i := range n {}, added in go1.22;
33-
// - replacing Split in "for range strings.Split(...)" by go1.24's
34-
// more efficient SplitSeq, or Fields with FieldSeq;
12+
// existing code by using more modern features of Go and its standard
13+
// library.
14+
//
15+
// Each diagnostic provides a fix. Our intent is that these fixes may
16+
// be safely applied en masse without changing the behavior of your
17+
// program. In some cases the suggested fixes are imperfect and may
18+
// lead to (for example) unused imports or unused local variables,
19+
// causing build breakage. However, these problems are generally
20+
// trivial to fix. We regard any modernizer whose fix changes program
21+
// behavior to have a serious bug and will endeavor to fix it.
3522
//
3623
// To apply all modernization fixes en masse, you can use the
3724
// following command:
3825
//
39-
// $ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./...
26+
// $ go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
4027
//
4128
// If the tool warns of conflicting fixes, you may need to run it more
4229
// than once until it has applied all fixes cleanly. This command is
4330
// not an officially supported interface and may change in the future.
31+
//
32+
// Changes produced by this tool should be reviewed as usual before
33+
// being merged. In some cases, a loop may be replaced by a simple
34+
// function call, causing comments within the loop to be discarded.
35+
// Human judgment may be required to avoid losing comments of value.
36+
//
37+
// Each diagnostic reported by modernize has a specific category. (The
38+
// categories are listed below.) Diagnostics in some categories, such
39+
// as "efaceany" (which replaces "interface{}" with "any" where it is
40+
// safe to do so) are particularly numerous. It may ease the burden of
41+
// code review to apply fixes in two passes, the first change
42+
// consisting only of fixes of category "efaceany", the second
43+
// consisting of all others. This can be achieved using the -category flag:
44+
//
45+
// $ modernize -category=efaceany -fix -test ./...
46+
// $ modernize -category=-efaceany -fix -test ./...
47+
//
48+
// Categories of modernize diagnostic:
49+
//
50+
// - minmax: replace an if/else conditional assignment by a call to
51+
// the built-in min or max functions added in go1.21.
52+
//
53+
// - sortslice: replace sort.Slice(x, func(i, j int) bool) { return s[i] < s[j] }
54+
// by a call to slices.Sort(s), added in go1.21.
55+
//
56+
// - efaceany: replace interface{} by the 'any' type added in go1.18.
57+
//
58+
// - slicesclone: replace append([]T(nil), s...) by slices.Clone(s) or
59+
// slices.Concat(s), added in go1.21.
60+
//
61+
// - mapsloop: replace a loop around an m[k]=v map update by a call
62+
// to one of the Collect, Copy, Clone, or Insert functions from
63+
// the maps package, added in go1.21.
64+
//
65+
// - fmtappendf: replace []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...),
66+
// added in go1.19.
67+
//
68+
// - testingcontext: replace uses of context.WithCancel in tests
69+
// with t.Context, added in go1.24.
70+
//
71+
// - omitzero: replace omitempty by omitzero on structs, added in go1.24.
72+
//
73+
// - bloop: replace "for i := range b.N" or "for range b.N" in a
74+
// benchmark with "for b.Loop()", and remove any preceding calls
75+
// to b.StopTimer, b.StartTimer, and b.ResetTimer.
76+
//
77+
// - slicesdelete: replace append(s[:i], s[i+1]...) by
78+
// slices.Delete(s, i, i+1), added in go1.21.
79+
//
80+
// - rangeint: replace a 3-clause "for i := 0; i < n; i++" loop by
81+
// "for i := range n", added in go1.22.
82+
//
83+
// - stringseq: replace Split in "for range strings.Split(...)" by go1.24's
84+
// more efficient SplitSeq, or Fields with FieldSeq.
4485
package modernize

gopls/internal/analysis/modernize/modernize.go

+34-8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"go/types"
1313
"iter"
1414
"regexp"
15+
"slices"
1516
"strings"
1617

1718
"golang.org/x/tools/go/analysis"
@@ -36,6 +37,15 @@ var Analyzer = &analysis.Analyzer{
3637
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize",
3738
}
3839

40+
// Stopgap until general solution in CL 655555 lands. A change to the
41+
// cmd/vet CLI requires a proposal whereas a change to an analyzer's
42+
// flag set does not.
43+
var category string
44+
45+
func init() {
46+
Analyzer.Flags.StringVar(&category, "category", "", "comma-separated list of categories to apply; with a leading '-', a list of categories to ignore")
47+
}
48+
3949
func run(pass *analysis.Pass) (any, error) {
4050
// Decorate pass.Report to suppress diagnostics in generated files.
4151
//
@@ -55,6 +65,10 @@ func run(pass *analysis.Pass) (any, error) {
5565
if diag.Category == "" {
5666
panic("Diagnostic.Category is unset")
5767
}
68+
// TODO(adonovan): stopgap until CL 655555 lands.
69+
if !enabledCategory(category, diag.Category) {
70+
return
71+
}
5872
if _, ok := generated[pass.Fset.File(diag.Pos)]; ok {
5973
return // skip checking if it's generated code
6074
}
@@ -76,14 +90,7 @@ func run(pass *analysis.Pass) (any, error) {
7690
sortslice(pass)
7791
testingContext(pass)
7892

79-
// TODO(adonovan):
80-
// - more modernizers here; see #70815.
81-
// - opt: interleave these micro-passes within a single inspection.
82-
// - solve the "duplicate import" problem (#68765) when a number of
83-
// fixes in the same file are applied in parallel and all add
84-
// the same import. The tests exhibit the problem.
85-
// - should all diagnostics be of the form "x can be modernized by y"
86-
// or is that a foolish consistency?
93+
// TODO(adonovan): opt: interleave these micro-passes within a single inspection.
8794

8895
return nil, nil
8996
}
@@ -159,3 +166,22 @@ var (
159166
byteSliceType = types.NewSlice(types.Typ[types.Byte])
160167
omitemptyRegex = regexp.MustCompile(`(?:^json| json):"[^"]*(,omitempty)(?:"|,[^"]*")\s?`)
161168
)
169+
170+
// enabledCategory reports whether a given category is enabled by the specified
171+
// filter. filter is a comma-separated list of categories, optionally prefixed
172+
// with `-` to disable all provided categories. All categories are enabled with
173+
// an empty filter.
174+
//
175+
// (Will be superseded by https://go.dev/cl/655555.)
176+
func enabledCategory(filter, category string) bool {
177+
if filter == "" {
178+
return true
179+
}
180+
// negation must be specified at the start
181+
filter, exclude := strings.CutPrefix(filter, "-")
182+
filters := strings.Split(filter, ",")
183+
if slices.Contains(filters, category) {
184+
return !exclude
185+
}
186+
return exclude
187+
}

0 commit comments

Comments
 (0)