| // Copyright 2024 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| /* |
| Package modernize provides a suite of analyzers that suggest |
| simplifications to Go code, using modern language and library |
| features. |
| |
| Each diagnostic provides a fix. Our intent is that these fixes may |
| be safely applied en masse without changing the behavior of your |
| program. In some cases the suggested fixes are imperfect and may |
| lead to (for example) unused imports or unused local variables, |
| causing build breakage. However, these problems are generally |
| trivial to fix. We regard any modernizer whose fix changes program |
| behavior to have a serious bug and will endeavor to fix it. |
| |
| To apply all modernization fixes en masse, you can use the |
| following command: |
| |
| $ go run golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest -fix ./... |
| |
| (Do not use "go get -tool" to add gopls as a dependency of your |
| module; gopls commands must be built from their release branch.) |
| |
| If the tool warns of conflicting fixes, you may need to run it more |
| than once until it has applied all fixes cleanly. This command is |
| not an officially supported interface and may change in the future. |
| |
| Changes produced by this tool should be reviewed as usual before |
| being merged. In some cases, a loop may be replaced by a simple |
| function call, causing comments within the loop to be discarded. |
| Human judgment may be required to avoid losing comments of value. |
| |
| The modernize suite contains many analyzers. Diagnostics from some, |
| such as "any" (which replaces "interface{}" with "any" where it |
| is safe to do so), are particularly numerous. It may ease the burden of |
| code review to apply fixes in two steps, the first consisting only of |
| fixes from the "any" analyzer, the second consisting of all |
| other analyzers. This can be achieved using flags, as in this example: |
| |
| $ modernize -any=true -fix ./... |
| $ modernize -any=false -fix ./... |
| |
| # Analyzer appendclipped |
| |
| appendclipped: simplify append chains using slices.Concat |
| |
| The appendclipped analyzer suggests replacing chains of append calls with a |
| single call to slices.Concat, which was added in Go 1.21. For example, |
| append(append(s, s1...), s2...) would be simplified to slices.Concat(s, s1, s2). |
| |
| In the simple case of appending to a newly allocated slice, such as |
| append([]T(nil), s...), the analyzer suggests the more concise slices.Clone(s). |
| For byte slices, it will prefer bytes.Clone if the "bytes" package is |
| already imported. |
| |
| This fix is only applied when the base of the append tower is a |
| "clipped" slice, meaning its length and capacity are equal (e.g. |
| x[:0:0] or []T{}). This is to avoid changing program behavior by |
| eliminating intended side effects on the base slice's underlying |
| array. |
| |
| This analyzer is currently disabled by default as the |
| transformation does not preserve the nilness of the base slice in |
| all cases; see https://go.dev/issue/73557. |
| |
| # Analyzer bloop |
| |
| bloop: replace for-range over b.N with b.Loop |
| |
| The bloop analyzer suggests replacing benchmark loops of the form |
| `for i := 0; i < b.N; i++` or `for range b.N` with the more modern |
| `for b.Loop()`, which was added in Go 1.24. |
| |
| This change makes benchmark code more readable and also removes the need for |
| manual timer control, so any preceding calls to b.StartTimer, b.StopTimer, |
| or b.ResetTimer within the same function will also be removed. |
| |
| Caveats: The b.Loop() method is designed to prevent the compiler from |
| optimizing away the benchmark loop, which can occasionally result in |
| slower execution due to increased allocations in some specific cases. |
| Since its fix may change the performance of nanosecond-scale benchmarks, |
| bloop is disabled by default in the `go fix` analyzer suite; see golang/go#74967. |
| |
| # Analyzer any |
| |
| any: replace interface{} with any |
| |
| The any analyzer suggests replacing uses of the empty interface type, |
| `interface{}`, with the `any` alias, which was introduced in Go 1.18. |
| This is a purely stylistic change that makes code more readable. |
| |
| # Analyzer errorsastype |
| |
| errorsastype: replace errors.As with errors.AsType[T] |
| |
| This analyzer suggests fixes to simplify uses of [errors.As] of |
| this form: |
| |
| var myerr *MyErr |
| if errors.As(err, &myerr) { |
| handle(myerr) |
| } |
| |
| by using the less error-prone generic [errors.AsType] function, |
| introduced in Go 1.26: |
| |
| if myerr, ok := errors.AsType[*MyErr](err); ok { |
| handle(myerr) |
| } |
| |
| The fix is only offered if the var declaration has the form shown and |
| there are no uses of myerr outside the if statement. |
| |
| # Analyzer fmtappendf |
| |
| fmtappendf: replace []byte(fmt.Sprintf) with fmt.Appendf |
| |
| The fmtappendf analyzer suggests replacing `[]byte(fmt.Sprintf(...))` with |
| `fmt.Appendf(nil, ...)`. This avoids the intermediate allocation of a string |
| by Sprintf, making the code more efficient. The suggestion also applies to |
| fmt.Sprint and fmt.Sprintln. |
| |
| # Analyzer forvar |
| |
| forvar: remove redundant re-declaration of loop variables |
| |
| The forvar analyzer removes unnecessary shadowing of loop variables. |
| Before Go 1.22, it was common to write `for _, x := range s { x := x ... }` |
| to create a fresh variable for each iteration. Go 1.22 changed the semantics |
| of `for` loops, making this pattern redundant. This analyzer removes the |
| unnecessary `x := x` statement. |
| |
| This fix only applies to `range` loops. |
| |
| # Analyzer mapsloop |
| |
| mapsloop: replace explicit loops over maps with calls to maps package |
| |
| The mapsloop analyzer replaces loops of the form |
| |
| for k, v := range x { m[k] = v } |
| |
| with a single call to a function from the `maps` package, added in Go 1.23. |
| Depending on the context, this could be `maps.Copy`, `maps.Insert`, |
| `maps.Clone`, or `maps.Collect`. |
| |
| The transformation to `maps.Clone` is applied conservatively, as it |
| preserves the nilness of the source map, which may be a subtle change in |
| behavior if the original code did not handle a nil map in the same way. |
| |
| # Analyzer minmax |
| |
| minmax: replace if/else statements with calls to min or max |
| |
| The minmax analyzer simplifies conditional assignments by suggesting the use |
| of the built-in `min` and `max` functions, introduced in Go 1.21. For example, |
| |
| if a < b { x = a } else { x = b } |
| |
| is replaced by |
| |
| x = min(a, b). |
| |
| This analyzer avoids making suggestions for floating-point types, |
| as the behavior of `min` and `max` with NaN values can differ from |
| the original if/else statement. |
| |
| # Analyzer newexpr |
| |
| newexpr: simplify code by using go1.26's new(expr) |
| |
| This analyzer finds declarations of functions of this form: |
| |
| func varOf(x int) *int { return &x } |
| |
| and suggests a fix to turn them into inlinable wrappers around |
| go1.26's built-in new(expr) function: |
| |
| //go:fix inline |
| func varOf(x int) *int { return new(x) } |
| |
| (The directive comment causes the 'inline' analyzer to suggest |
| that calls to such functions are inlined.) |
| |
| In addition, this analyzer suggests a fix for each call |
| to one of the functions before it is transformed, so that |
| |
| use(varOf(123)) |
| |
| is replaced by: |
| |
| use(new(123)) |
| |
| Wrapper functions such as varOf are common when working with Go |
| serialization packages such as for JSON or protobuf, where pointers |
| are often used to express optionality. |
| |
| # Analyzer omitzero |
| |
| omitzero: suggest replacing omitempty with omitzero for struct fields |
| |
| The omitzero analyzer identifies uses of the `omitempty` JSON struct |
| tag on fields that are themselves structs. For struct-typed fields, |
| the `omitempty` tag has no effect on the behavior of json.Marshal and |
| json.Unmarshal. The analyzer offers two suggestions: either remove the |
| tag, or replace it with `omitzero` (added in Go 1.24), which correctly |
| omits the field if the struct value is zero. |
| |
| However, some other serialization packages (notably kubebuilder, see |
| https://book.kubebuilder.io/reference/markers.html) may have their own |
| interpretation of the `json:",omitzero"` tag, so removing it may affect |
| program behavior. For this reason, the omitzero modernizer will not |
| make changes in any package that contains +kubebuilder annotations. |
| |
| Replacing `omitempty` with `omitzero` is a change in behavior. The |
| original code would always encode the struct field, whereas the |
| modified code will omit it if it is a zero-value. |
| |
| # Analyzer plusbuild |
| |
| plusbuild: remove obsolete //+build comments |
| |
| The plusbuild analyzer suggests a fix to remove obsolete build tags |
| of the form: |
| |
| //+build linux,amd64 |
| |
| in files that also contain a Go 1.18-style tag such as: |
| |
| //go:build linux && amd64 |
| |
| (It does not check that the old and new tags are consistent; |
| that is the job of the 'buildtag' analyzer in the vet suite.) |
| |
| # Analyzer rangeint |
| |
| rangeint: replace 3-clause for loops with for-range over integers |
| |
| The rangeint analyzer suggests replacing traditional for loops such |
| as |
| |
| for i := 0; i < n; i++ { ... } |
| |
| with the more idiomatic Go 1.22 style: |
| |
| for i := range n { ... } |
| |
| This transformation is applied only if (a) the loop variable is not |
| modified within the loop body and (b) the loop's limit expression |
| is not modified within the loop, as `for range` evaluates its |
| operand only once. |
| |
| # Analyzer reflecttypefor |
| |
| reflecttypefor: replace reflect.TypeOf(x) with TypeFor[T]() |
| |
| This analyzer suggests fixes to replace uses of reflect.TypeOf(x) with |
| reflect.TypeFor, introduced in go1.22, when the desired runtime type |
| is known at compile time, for example: |
| |
| reflect.TypeOf(uint32(0)) -> reflect.TypeFor[uint32]() |
| reflect.TypeOf((*ast.File)(nil)) -> reflect.TypeFor[*ast.File]() |
| |
| It also offers a fix to simplify the construction below, which uses |
| reflect.TypeOf to return the runtime type for an interface type, |
| |
| reflect.TypeOf((*io.Reader)(nil)).Elem() |
| |
| to: |
| |
| reflect.TypeFor[io.Reader]() |
| |
| No fix is offered in cases when the runtime type is dynamic, such as: |
| |
| var r io.Reader = ... |
| reflect.TypeOf(r) |
| |
| or when the operand has potential side effects. |
| |
| # Analyzer slicescontains |
| |
| slicescontains: replace loops with slices.Contains or slices.ContainsFunc |
| |
| The slicescontains analyzer simplifies loops that check for the existence of |
| an element in a slice. It replaces them with calls to `slices.Contains` or |
| `slices.ContainsFunc`, which were added in Go 1.21. |
| |
| If the expression for the target element has side effects, this |
| transformation will cause those effects to occur only once, not |
| once per tested slice element. |
| |
| # Analyzer slicesdelete |
| |
| slicesdelete: replace append-based slice deletion with slices.Delete |
| |
| The slicesdelete analyzer suggests replacing the idiom |
| |
| s = append(s[:i], s[j:]...) |
| |
| with the more explicit |
| |
| s = slices.Delete(s, i, j) |
| |
| introduced in Go 1.21. |
| |
| This analyzer is disabled by default. The `slices.Delete` function |
| zeros the elements between the new length and the old length of the |
| slice to prevent memory leaks, which is a subtle difference in |
| behavior compared to the append-based idiom; see https://go.dev/issue/73686. |
| |
| # Analyzer slicessort |
| |
| slicessort: replace sort.Slice with slices.Sort for basic types |
| |
| The slicessort analyzer simplifies sorting slices of basic ordered |
| types. It replaces |
| |
| sort.Slice(s, func(i, j int) bool { return s[i] < s[j] }) |
| |
| with the simpler `slices.Sort(s)`, which was added in Go 1.21. |
| |
| # Analyzer stditerators |
| |
| stditerators: use iterators instead of Len/At-style APIs |
| |
| This analyzer suggests a fix to replace each loop of the form: |
| |
| for i := 0; i < x.Len(); i++ { |
| use(x.At(i)) |
| } |
| |
| or its "for elem := range x.Len()" equivalent by a range loop over an |
| iterator offered by the same data type: |
| |
| for elem := range x.All() { |
| use(x.At(i) |
| } |
| |
| where x is one of various well-known types in the standard library. |
| |
| # Analyzer stringscut |
| |
| stringscut: replace strings.Index etc. with strings.Cut |
| |
| This analyzer replaces certain patterns of use of [strings.Index] and string slicing by [strings.Cut], added in go1.18. |
| |
| For example: |
| |
| idx := strings.Index(s, substr) |
| if idx >= 0 { |
| return s[:idx] |
| } |
| |
| is replaced by: |
| |
| before, _, ok := strings.Cut(s, substr) |
| if ok { |
| return before |
| } |
| |
| And: |
| |
| idx := strings.Index(s, substr) |
| if idx >= 0 { |
| return |
| } |
| |
| is replaced by: |
| |
| found := strings.Contains(s, substr) |
| if found { |
| return |
| } |
| |
| It also handles variants using [strings.IndexByte] instead of Index, or the bytes package instead of strings. |
| |
| Fixes are offered only in cases in which there are no potential modifications of the idx, s, or substr expressions between their definition and use. |
| |
| # Analyzer stringscutprefix |
| |
| stringscutprefix: replace HasPrefix/TrimPrefix with CutPrefix |
| |
| The stringscutprefix analyzer simplifies a common pattern where code first |
| checks for a prefix with `strings.HasPrefix` and then removes it with |
| `strings.TrimPrefix`. It replaces this two-step process with a single call |
| to `strings.CutPrefix`, introduced in Go 1.20. The analyzer also handles |
| the equivalent functions in the `bytes` package. |
| |
| For example, this input: |
| |
| if strings.HasPrefix(s, prefix) { |
| use(strings.TrimPrefix(s, prefix)) |
| } |
| |
| is fixed to: |
| |
| if after, ok := strings.CutPrefix(s, prefix); ok { |
| use(after) |
| } |
| |
| The analyzer also offers fixes to use CutSuffix in a similar way. |
| This input: |
| |
| if strings.HasSuffix(s, suffix) { |
| use(strings.TrimSuffix(s, suffix)) |
| } |
| |
| is fixed to: |
| |
| if before, ok := strings.CutSuffix(s, suffix); ok { |
| use(before) |
| } |
| |
| # Analyzer stringsseq |
| |
| stringsseq: replace ranging over Split/Fields with SplitSeq/FieldsSeq |
| |
| The stringsseq analyzer improves the efficiency of iterating over substrings. |
| It replaces |
| |
| for range strings.Split(...) |
| |
| with the more efficient |
| |
| for range strings.SplitSeq(...) |
| |
| which was added in Go 1.24 and avoids allocating a slice for the |
| substrings. The analyzer also handles strings.Fields and the |
| equivalent functions in the bytes package. |
| |
| # Analyzer stringsbuilder |
| |
| stringsbuilder: replace += with strings.Builder |
| |
| This analyzer replaces repeated string += string concatenation |
| operations with calls to Go 1.10's strings.Builder. |
| |
| For example: |
| |
| var s = "[" |
| for x := range seq { |
| s += x |
| s += "." |
| } |
| s += "]" |
| use(s) |
| |
| is replaced by: |
| |
| var s strings.Builder |
| s.WriteString("[") |
| for x := range seq { |
| s.WriteString(x) |
| s.WriteString(".") |
| } |
| s.WriteString("]") |
| use(s.String()) |
| |
| This avoids quadratic memory allocation and improves performance. |
| |
| The analyzer requires that all references to s except the final one |
| are += operations. To avoid warning about trivial cases, at least one |
| must appear within a loop. The variable s must be a local |
| variable, not a global or parameter. |
| |
| The sole use of the finished string must be the last reference to the |
| variable s. (It may appear within an intervening loop or function literal, |
| since even s.String() is called repeatedly, it does not allocate memory.) |
| |
| Often the addend is a call to fmt.Sprintf, as in this example: |
| |
| var s string |
| for x := range seq { |
| s += fmt.Sprintf("%v", x) |
| } |
| |
| which, once the suggested fix is applied, becomes: |
| |
| var s strings.Builder |
| for x := range seq { |
| s.WriteString(fmt.Sprintf("%v", x)) |
| } |
| |
| The WriteString call can be further simplified to the more efficient |
| fmt.Fprintf(&s, "%v", x), avoiding the allocation of an intermediary. |
| However, stringsbuilder does not perform this simplification; |
| it requires staticcheck analyzer QF1012. (See https://go.dev/issue/76918.) |
| |
| # Analyzer testingcontext |
| |
| testingcontext: replace context.WithCancel with t.Context in tests |
| |
| The testingcontext analyzer simplifies context management in tests. It |
| replaces the manual creation of a cancellable context, |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| with a single call to t.Context(), which was added in Go 1.24. |
| |
| This change is only suggested if the `cancel` function is not used |
| for any other purpose. |
| |
| # Analyzer unsafefuncs |
| |
| unsafefuncs: replace unsafe pointer arithmetic with function calls |
| |
| The unsafefuncs analyzer simplifies pointer arithmetic expressions by |
| replacing them with calls to helper functions such as unsafe.Add, |
| added in Go 1.17. |
| |
| Example: |
| |
| unsafe.Pointer(uintptr(ptr) + uintptr(n)) |
| |
| where ptr is an unsafe.Pointer, is replaced by: |
| |
| unsafe.Add(ptr, n) |
| |
| # Analyzer waitgroup |
| |
| waitgroup: replace wg.Add(1)/go/wg.Done() with wg.Go |
| |
| The waitgroup analyzer simplifies goroutine management with `sync.WaitGroup`. |
| It replaces the common pattern |
| |
| wg.Add(1) |
| go func() { |
| defer wg.Done() |
| ... |
| }() |
| |
| with a single call to |
| |
| wg.Go(func(){ ... }) |
| |
| which was added in Go 1.25. |
| */ |
| package modernize |