|
| 1 | +// Copyright 2024 The Go Authors. All rights reserved. |
| 2 | +// Use of this source code is governed by a BSD-style |
| 3 | +// license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +package modernize |
| 6 | + |
| 7 | +import ( |
| 8 | + "go/ast" |
| 9 | + "go/constant" |
| 10 | + "go/token" |
| 11 | + "go/types" |
| 12 | + |
| 13 | + "golang.org/x/tools/go/analysis" |
| 14 | + "golang.org/x/tools/go/analysis/passes/inspect" |
| 15 | + "golang.org/x/tools/go/ast/inspector" |
| 16 | +) |
| 17 | + |
| 18 | +// The slicesdelete pass attempts to replace instances of append(s[:i], s[i+k:]...) |
| 19 | +// with slices.Delete(s, i, i+k) where k is some positive constant. |
| 20 | +// Other variations that will also have suggested replacements include: |
| 21 | +// append(s[:i-1], s[i:]...) and append(s[:i+k1], s[i+k2:]) where k2 > k1. |
| 22 | +func slicesdelete(pass *analysis.Pass) { |
| 23 | + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) |
| 24 | + info := pass.TypesInfo |
| 25 | + report := func(call *ast.CallExpr, slice1, slice2 *ast.SliceExpr) { |
| 26 | + pass.Report(analysis.Diagnostic{ |
| 27 | + Pos: call.Pos(), |
| 28 | + End: call.End(), |
| 29 | + Category: "slicesdelete", |
| 30 | + Message: "Replace append with slices.Delete", |
| 31 | + SuggestedFixes: []analysis.SuggestedFix{{ |
| 32 | + Message: "Replace append with slices.Delete", |
| 33 | + TextEdits: []analysis.TextEdit{ |
| 34 | + // Change name of called function. |
| 35 | + { |
| 36 | + Pos: call.Fun.Pos(), |
| 37 | + End: call.Fun.End(), |
| 38 | + NewText: []byte("slices.Delete"), |
| 39 | + }, |
| 40 | + // Delete ellipsis. |
| 41 | + { |
| 42 | + Pos: call.Ellipsis, |
| 43 | + End: call.Ellipsis + token.Pos(len("...")), // delete ellipsis |
| 44 | + }, |
| 45 | + // Remove second slice variable name. |
| 46 | + { |
| 47 | + Pos: slice2.X.Pos(), |
| 48 | + End: slice2.X.End(), |
| 49 | + }, |
| 50 | + // Insert after first slice variable name. |
| 51 | + { |
| 52 | + Pos: slice1.X.End(), |
| 53 | + NewText: []byte(", "), |
| 54 | + }, |
| 55 | + // Remove brackets and colons. |
| 56 | + { |
| 57 | + Pos: slice1.Lbrack, |
| 58 | + End: slice1.High.Pos(), |
| 59 | + }, |
| 60 | + { |
| 61 | + Pos: slice1.Rbrack, |
| 62 | + End: slice1.Rbrack + 1, |
| 63 | + }, |
| 64 | + { |
| 65 | + Pos: slice2.Lbrack, |
| 66 | + End: slice2.Lbrack + 1, |
| 67 | + }, |
| 68 | + { |
| 69 | + Pos: slice2.Low.End(), |
| 70 | + End: slice2.Rbrack + 1, |
| 71 | + }, |
| 72 | + }, |
| 73 | + }}, |
| 74 | + }) |
| 75 | + } |
| 76 | + for curFile := range filesUsing(inspect, info, "go1.21") { |
| 77 | + for curCall := range curFile.Preorder((*ast.CallExpr)(nil)) { |
| 78 | + call := curCall.Node().(*ast.CallExpr) |
| 79 | + if id, ok := call.Fun.(*ast.Ident); ok && len(call.Args) == 2 { |
| 80 | + // Verify we have append with two slices and ... operator, |
| 81 | + // the first slice has no low index and second slice has no |
| 82 | + // high index, and not a three-index slice. |
| 83 | + if call.Ellipsis.IsValid() && info.Uses[id] == builtinAppend { |
| 84 | + slice1, ok1 := call.Args[0].(*ast.SliceExpr) |
| 85 | + slice2, ok2 := call.Args[1].(*ast.SliceExpr) |
| 86 | + if ok1 && slice1.Low == nil && !slice1.Slice3 && |
| 87 | + ok2 && slice2.High == nil && !slice2.Slice3 && |
| 88 | + equalSyntax(slice1.X, slice2.X) && |
| 89 | + increasingSliceIndices(info, slice1.High, slice2.Low) { |
| 90 | + // Have append(s[:a], s[b:]...) where we can verify a < b. |
| 91 | + report(call, slice1, slice2) |
| 92 | + } |
| 93 | + } |
| 94 | + } |
| 95 | + } |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +// Given two slice indices a and b, returns true if we can verify that a < b. |
| 100 | +// It recognizes certain forms such as i+k1 < i+k2 where k1 < k2. |
| 101 | +func increasingSliceIndices(info *types.Info, a, b ast.Expr) bool { |
| 102 | + |
| 103 | + // Given an expression of the form i±k, returns (i, k) |
| 104 | + // where k is a signed constant. Otherwise it returns (e, 0). |
| 105 | + split := func(e ast.Expr) (ast.Expr, constant.Value) { |
| 106 | + if binary, ok := e.(*ast.BinaryExpr); ok && (binary.Op == token.SUB || binary.Op == token.ADD) { |
| 107 | + // Negate constants if operation is subtract instead of add |
| 108 | + if k := info.Types[binary.Y].Value; k != nil { |
| 109 | + return binary.X, constant.UnaryOp(binary.Op, k, 0) // i ± k |
| 110 | + } |
| 111 | + } |
| 112 | + return e, constant.MakeInt64(0) |
| 113 | + } |
| 114 | + |
| 115 | + // Handle case where either a or b is a constant |
| 116 | + ak := info.Types[a].Value |
| 117 | + bk := info.Types[b].Value |
| 118 | + if ak != nil || bk != nil { |
| 119 | + return ak != nil && bk != nil && constant.Compare(ak, token.LSS, bk) |
| 120 | + } |
| 121 | + |
| 122 | + ai, ak := split(a) |
| 123 | + bi, bk := split(b) |
| 124 | + return equalSyntax(ai, bi) && constant.Compare(ak, token.LSS, bk) |
| 125 | +} |
0 commit comments