| // Copyright 2025 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 slicesbackward |
| |
| import ( |
| "slices" |
| "sync" |
| ) |
| |
| var _ = slices.Backward[[]int] // force import of "slices" to avoid duplicate import edits |
| |
| // Basic: only use of i is s[i] — should suggest "for _, v := range slices.Backward(s)" |
| func onlyIndexUse(s []int) { |
| for i := len(s) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward" |
| println(s[i]) |
| } |
| } |
| |
| // Tests for the heuristic that generates a name for the value variable. |
| |
| // Should use "name" for the value variable, and should remove the declaration. |
| func firstUseName(s []int) { |
| for i := len(s) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward" |
| name := s[i] |
| println(name) |
| name2 := s[i] |
| println(name2) |
| } |
| } |
| |
| func useName(s []int) { |
| for i := len(s) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward" |
| println(i) |
| name := s[i] |
| println(name) |
| } |
| } |
| |
| // Can't use "name" because it is declared outside the block, and no singular |
| // form of "arr", so use the first letter ("a"). |
| func firstLetter(arr []int) { |
| name := 2 |
| for i := len(arr) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward" |
| name = arr[i] |
| } |
| println(name) |
| } |
| |
| // Slice expression is a selector and not a valid identifier. |
| // Should fallback to "v" instead of trying to use "s.item". |
| func indexUseSelector(s struct{ items []int }) { |
| for i := len(s.items) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward" |
| println(s.items[i]) |
| } |
| } |
| |
| // Index used for something other than s[i] — keep both i and v. |
| func indexUsedElsewhere(s []int) { |
| for i := len(s) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward" |
| println(i, s[i]) |
| } |
| } |
| |
| // Index used only for non-slice purpose (no s[i] at all). |
| func indexNoSliceAccess(s []int) { |
| for i := len(s) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward" |
| println(i) |
| } |
| } |
| |
| // Should NOT fire: condition is i > 0, not i >= 0. |
| func condGT(s []int) { |
| for i := len(s) - 1; i > 0; i-- { |
| println(s[i]) |
| } |
| } |
| |
| // Should NOT fire: post is i -= 2. |
| func postNotDec(s []int) { |
| for i := len(s) - 1; i >= 0; i -= 2 { |
| println(s[i]) |
| } |
| } |
| |
| // Should NOT fire: init is not len(s) - 1. |
| func initNotLenMinus1(s []int) { |
| for i := len(s) - 2; i >= 0; i-- { |
| println(s[i]) |
| } |
| } |
| |
| // Should NOT fire: i is assigned inside the body. |
| func indexAssignedInBody(s []int) { |
| for i := len(s) - 1; i >= 0; i-- { |
| i = i - 1 // nolint: ignore for test |
| println(s[i]) |
| } |
| } |
| |
| // Should work with a named slice variable. |
| func namedSlice() { |
| nums := []string{"a", "b", "c"} |
| for i := len(nums) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward" |
| println(nums[i]) |
| } |
| } |
| |
| // Should NOT fire: init assigns to a pre-existing variable. |
| func iDeclaredBeforeLoop(s []int) { |
| var i int |
| for i = len(s) - 1; i >= 0; i-- { |
| println(s[i]) |
| } |
| _ = i |
| } |
| |
| // Should NOT fire: the value of a pre-existing index variable is observable |
| // after the loop and must remain -1 after the original loop. |
| func preexistingIndexValue(s []int) int { |
| i := 123 |
| for i = len(s) - 1; i >= 0; i-- { |
| println(s[i]) |
| } |
| return i |
| } |
| |
| // Should NOT fire: i is address-taken before the loop (init uses =, not :=). |
| func iAddressTakenBeforeLoop(s []int) { |
| var i int |
| p := &i |
| for i = len(s) - 1; i >= 0; i-- { |
| println(s[i]) |
| } |
| _ = p |
| } |
| |
| // Should NOT fire: an index expression is used as an lvalue (slice mutation) |
| func indexExprAssign(s []int) { |
| for i := len(s) - 1; i >= 0; i-- { |
| _ = s[i] |
| s[i] = 0 |
| } |
| } |
| |
| // Should NOT fire: an index expression is used as an lvalue (slice mutation) |
| func indexExprAssignWithOp(s []int) { |
| for i := len(s) - 1; i >= 0; i-- { |
| _ = s[i] |
| s[i] += 1 |
| s[i] -= 1 |
| s[i] *= 1 |
| s[i] /= 1 |
| } |
| } |
| |
| // Should NOT fire: an index expression is used as an lvalue (slice mutation) |
| func indexExprIncDec(s []int) { |
| for i := len(s) - 1; i >= 0; i-- { |
| _ = s[i] |
| s[i]++ |
| s[i]-- |
| } |
| } |
| |
| // Should NOT fire: an index expression is address-taken |
| func indexExprAddr(s []int) { |
| for i := len(s) - 1; i >= 0; i-- { |
| _ = &s[i] |
| } |
| } |
| |
| type item struct { |
| n int |
| } |
| |
| // Should NOT fire: field mutation |
| func indexExprMutated(s []item) { |
| for i := len(s) - 1; i >= 0; i-- { |
| s[i].n++ |
| } |
| } |
| |
| type outer struct { |
| inner item |
| } |
| |
| // Should NOT fire: nested field mutation |
| func indexExprNestedField(s []outer) { |
| for i := len(s) - 1; i >= 0; i-- { |
| s[i].inner.n++ |
| } |
| } |
| |
| // Should NOT fire: field assignment |
| func indexExprFieldAssign(s []item) { |
| for i := len(s) - 1; i >= 0; i-- { |
| s[i].n = 5 |
| } |
| } |
| |
| // Should NOT fire: multi-field assignment |
| func indexExprMultiAssign(s []int) { |
| for i := len(s) - 2; i >= 0; i-- { |
| s[i], s[i+1] = 1, 2 |
| } |
| } |
| |
| // Should NOT fire: parenthesized assignment |
| func indexExprParenthesizedAssign(s []int) { |
| for i := len(s) - 1; i >= 0; i-- { |
| (s[i]) = 5 |
| } |
| } |
| |
| // Should NOT fire: address-taken |
| func indexExprFieldAddr(s []item) { |
| for i := len(s) - 1; i >= 0; i-- { |
| _ = &s[i].n |
| } |
| } |
| |
| // Should NOT fire: method call with pointer receiver may mutate element |
| func indexExprMethodCall(s []sync.Mutex) { |
| for i := len(s) - 1; i >= 0; i-- { |
| s[i].Lock() |
| s[i].Unlock() |
| } |
| } |
| |
| type stringer struct{} |
| |
| func (stringer) String() string { return "" } |
| |
| func (*stringer) StringPtr() string { return "" } |
| |
| // SHOULD fire: method call with value receiver does not mutate element |
| func indexExprValueReceiver(s []stringer) { |
| for i := len(s) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward" |
| _ = s[i].String() |
| } |
| } |
| |
| // SHOULD fire: method call with pointer receiver type and pointer receiver value does not mutate |
| func indexExprPtrValueReceiver(s []*stringer) { |
| for i := len(s) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward" |
| _ = s[i].StringPtr() |
| } |
| } |
| |
| // Should NOT fire: range assignment mutating slice elements |
| func indexExprRangeAssign(s []int, x []int) { |
| for i := len(s) - 1; i >= 0; i-- { |
| for s[i] = range x { |
| } |
| } |
| } |
| |
| // SHOULD fire: slice of slices is indirect reference |
| func indexExprSliceOfSlices(s [][]int) { |
| for i := len(s) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward" |
| s[i][0]++ |
| } |
| } |
| |
| // SHOULD fire: slice of pointers is indirect reference |
| func indexExprPointerSlice(s []*item) { |
| for i := len(s) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward" |
| s[i].n++ |
| } |
| } |
| |
| // Should NOT fire: slice of arrays, mutating array element is direct mutation |
| func indexExprSliceOfArrays(s [][3]int) { |
| for i := len(s) - 1; i >= 0; i-- { |
| s[i][0]++ |
| } |
| } |
| |
| // SHOULD fire: index expression used as index of another slice which is mutated |
| func indexExprUsedAsIndex(s []int) { |
| for i := len(s) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward" |
| s[s[i]] = 5 |
| } |
| } |