1
2
3
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/internal/analysis/analyzerutil"
16 "golang.org/x/tools/internal/astutil"
17 "golang.org/x/tools/internal/refactor"
18 "golang.org/x/tools/internal/typesinternal"
19 "golang.org/x/tools/internal/versions"
20 )
21
22
23 var SlicesDeleteAnalyzer = &analysis.Analyzer{
24 Name: "slicesdelete",
25 Doc: analyzerutil.MustExtractDoc(doc, "slicesdelete"),
26 Requires: []*analysis.Analyzer{inspect.Analyzer},
27 Run: slicesdelete,
28 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicesdelete",
29 }
30
31
32
33
34
35 func slicesdelete(pass *analysis.Pass) (any, error) {
36
37
38 if within(pass, "slices", "runtime") {
39 return nil, nil
40 }
41
42 info := pass.TypesInfo
43 report := func(file *ast.File, call *ast.CallExpr, slice1, slice2 *ast.SliceExpr) {
44 insert := func(pos token.Pos, text string) analysis.TextEdit {
45 return analysis.TextEdit{Pos: pos, End: pos, NewText: []byte(text)}
46 }
47 isIntExpr := func(e ast.Expr) bool {
48 return types.Identical(types.Default(info.TypeOf(e)), builtinInt.Type())
49 }
50 isIntShadowed := func() bool {
51 scope := info.Scopes[file].Innermost(call.Lparen)
52 if _, obj := scope.LookupParent("int", call.Lparen); obj != builtinInt {
53 return true
54 }
55 return false
56 }
57
58 prefix, edits := refactor.AddImport(info, file, "slices", "slices", "Delete", call.Pos())
59
60
61 if isIntShadowed() && (!isIntExpr(slice1.High) || !isIntExpr(slice2.Low)) {
62 return
63 }
64 if !isIntExpr(slice1.High) {
65 edits = append(edits,
66 insert(slice1.High.Pos(), "int("),
67 insert(slice1.High.End(), ")"),
68 )
69 }
70 if !isIntExpr(slice2.Low) {
71 edits = append(edits,
72 insert(slice2.Low.Pos(), "int("),
73 insert(slice2.Low.End(), ")"),
74 )
75 }
76
77 pass.Report(analysis.Diagnostic{
78 Pos: call.Pos(),
79 End: call.End(),
80 Message: "Replace append with slices.Delete",
81 SuggestedFixes: []analysis.SuggestedFix{{
82 Message: "Replace append with slices.Delete",
83 TextEdits: append(edits, []analysis.TextEdit{
84
85 {
86 Pos: call.Fun.Pos(),
87 End: call.Fun.End(),
88 NewText: []byte(prefix + "Delete"),
89 },
90
91 {
92 Pos: call.Ellipsis,
93 End: call.Ellipsis + token.Pos(len("...")),
94 },
95
96 {
97 Pos: slice2.X.Pos(),
98 End: slice2.X.End(),
99 },
100
101 {
102 Pos: slice1.X.End(),
103 NewText: []byte(", "),
104 },
105
106 {
107 Pos: slice1.Lbrack,
108 End: slice1.High.Pos(),
109 },
110 {
111 Pos: slice1.Rbrack,
112 End: slice1.Rbrack + 1,
113 },
114 {
115 Pos: slice2.Lbrack,
116 End: slice2.Lbrack + 1,
117 },
118 {
119 Pos: slice2.Low.End(),
120 End: slice2.Rbrack + 1,
121 },
122 }...),
123 }},
124 })
125 }
126 for curFile := range filesUsingGoVersion(pass, versions.Go1_21) {
127 file := curFile.Node().(*ast.File)
128 for curCall := range curFile.Preorder((*ast.CallExpr)(nil)) {
129 call := curCall.Node().(*ast.CallExpr)
130 if id, ok := call.Fun.(*ast.Ident); ok && len(call.Args) == 2 {
131
132
133
134 if call.Ellipsis.IsValid() && info.Uses[id] == builtinAppend {
135 slice1, ok1 := call.Args[0].(*ast.SliceExpr)
136 slice2, ok2 := call.Args[1].(*ast.SliceExpr)
137 if ok1 && slice1.Low == nil && !slice1.Slice3 &&
138 ok2 && slice2.High == nil && !slice2.Slice3 &&
139 astutil.EqualSyntax(slice1.X, slice2.X) &&
140 typesinternal.NoEffects(info, slice1.X) &&
141 increasingSliceIndices(info, slice1.High, slice2.Low) {
142
143 report(file, call, slice1, slice2)
144 }
145 }
146 }
147 }
148 }
149 return nil, nil
150 }
151
152
153
154 func increasingSliceIndices(info *types.Info, a, b ast.Expr) bool {
155
156
157 split := func(e ast.Expr) (ast.Expr, constant.Value) {
158 if binary, ok := e.(*ast.BinaryExpr); ok && (binary.Op == token.SUB || binary.Op == token.ADD) {
159
160 if k := info.Types[binary.Y].Value; k != nil {
161 return binary.X, constant.UnaryOp(binary.Op, k, 0)
162 }
163 }
164 return e, constant.MakeInt64(0)
165 }
166
167
168 ak := info.Types[a].Value
169 bk := info.Types[b].Value
170 if ak != nil || bk != nil {
171 return ak != nil && bk != nil && constant.Compare(ak, token.LSS, bk)
172 }
173
174 ai, ak := split(a)
175 bi, bk := split(b)
176 return astutil.EqualSyntax(ai, bi) && constant.Compare(ak, token.LSS, bk)
177 }
178
View as plain text