1
2
3
4
5 package modernize
6
7 import (
8 "fmt"
9 "go/ast"
10 "go/token"
11 "strings"
12
13 "golang.org/x/tools/go/analysis"
14 "golang.org/x/tools/go/analysis/passes/inspect"
15 "golang.org/x/tools/go/types/typeutil"
16 "golang.org/x/tools/internal/analysis/analyzerutil"
17 typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
18 "golang.org/x/tools/internal/astutil"
19 "golang.org/x/tools/internal/refactor"
20 "golang.org/x/tools/internal/typesinternal"
21 "golang.org/x/tools/internal/typesinternal/typeindex"
22 "golang.org/x/tools/internal/versions"
23 )
24
25 var StringsCutPrefixAnalyzer = &analysis.Analyzer{
26 Name: "stringscutprefix",
27 Doc: analyzerutil.MustExtractDoc(doc, "stringscutprefix"),
28 Requires: []*analysis.Analyzer{
29 inspect.Analyzer,
30 typeindexanalyzer.Analyzer,
31 },
32 Run: stringscutprefix,
33 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stringscutprefix",
34 }
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56 func stringscutprefix(pass *analysis.Pass) (any, error) {
57 var (
58 index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
59 info = pass.TypesInfo
60
61 stringsTrimPrefix = index.Object("strings", "TrimPrefix")
62 bytesTrimPrefix = index.Object("bytes", "TrimPrefix")
63 stringsTrimSuffix = index.Object("strings", "TrimSuffix")
64 bytesTrimSuffix = index.Object("bytes", "TrimSuffix")
65 )
66 if !index.Used(stringsTrimPrefix, bytesTrimPrefix, stringsTrimSuffix, bytesTrimSuffix) {
67 return nil, nil
68 }
69
70 for curFile := range filesUsingGoVersion(pass, versions.Go1_20) {
71 for curIfStmt := range curFile.Preorder((*ast.IfStmt)(nil)) {
72 ifStmt := curIfStmt.Node().(*ast.IfStmt)
73
74
75 if call, ok := ifStmt.Cond.(*ast.CallExpr); ok && ifStmt.Init == nil && len(ifStmt.Body.List) > 0 {
76
77 obj := typeutil.Callee(info, call)
78 if !typesinternal.IsFunctionNamed(obj, "strings", "HasPrefix", "HasSuffix") &&
79 !typesinternal.IsFunctionNamed(obj, "bytes", "HasPrefix", "HasSuffix") {
80 continue
81 }
82 isPrefix := strings.HasSuffix(obj.Name(), "Prefix")
83
84
85
86 firstStmt := curIfStmt.Child(ifStmt.Body).Child(ifStmt.Body.List[0])
87 for curCall := range firstStmt.Preorder((*ast.CallExpr)(nil)) {
88 call1 := curCall.Node().(*ast.CallExpr)
89 obj1 := typeutil.Callee(info, call1)
90
91
92 if obj1 == nil ||
93 obj1 != stringsTrimPrefix && obj1 != bytesTrimPrefix &&
94 obj1 != stringsTrimSuffix && obj1 != bytesTrimSuffix {
95 continue
96 }
97
98 isPrefix1 := strings.HasSuffix(obj1.Name(), "Prefix")
99 var cutFuncName, varName, message, fixMessage string
100 if isPrefix && isPrefix1 {
101 cutFuncName = "CutPrefix"
102 varName = "after"
103 message = "HasPrefix + TrimPrefix can be simplified to CutPrefix"
104 fixMessage = "Replace HasPrefix/TrimPrefix with CutPrefix"
105 } else if !isPrefix && !isPrefix1 {
106 cutFuncName = "CutSuffix"
107 varName = "before"
108 message = "HasSuffix + TrimSuffix can be simplified to CutSuffix"
109 fixMessage = "Replace HasSuffix/TrimSuffix with CutSuffix"
110 } else {
111 continue
112 }
113
114
115 var (
116 s0 = call.Args[0]
117 pre0 = call.Args[1]
118 s = call1.Args[0]
119 pre = call1.Args[1]
120 )
121
122
123
124 if astutil.EqualSyntax(s0, s) && astutil.EqualSyntax(pre0, pre) {
125 after := refactor.FreshName(info.Scopes[ifStmt], ifStmt.Pos(), varName)
126 prefix, importEdits := refactor.AddImport(
127 info,
128 curFile.Node().(*ast.File),
129 obj1.Pkg().Name(),
130 obj1.Pkg().Path(),
131 cutFuncName,
132 call.Pos(),
133 )
134 okVarName := refactor.FreshName(info.Scopes[ifStmt], ifStmt.Pos(), "ok")
135 pass.Report(analysis.Diagnostic{
136
137 Pos: call.Pos(),
138 End: call.End(),
139 Message: message,
140 SuggestedFixes: []analysis.SuggestedFix{{
141 Message: fixMessage,
142
143
144
145
146 TextEdits: append(importEdits, []analysis.TextEdit{
147 {
148 Pos: call.Fun.Pos(),
149 End: call.Fun.Pos(),
150 NewText: fmt.Appendf(nil, "%s, %s :=", after, okVarName),
151 },
152 {
153 Pos: call.Fun.Pos(),
154 End: call.Fun.End(),
155 NewText: fmt.Appendf(nil, "%s%s", prefix, cutFuncName),
156 },
157 {
158 Pos: call.End(),
159 End: call.End(),
160 NewText: fmt.Appendf(nil, "; %s ", okVarName),
161 },
162 {
163 Pos: call1.Pos(),
164 End: call1.End(),
165 NewText: []byte(after),
166 },
167 }...),
168 }}},
169 )
170 break
171 }
172 }
173 }
174
175
176 if bin, ok := ifStmt.Cond.(*ast.BinaryExpr); ok &&
177 bin.Op == token.NEQ &&
178 ifStmt.Init != nil &&
179 isSimpleAssign(ifStmt.Init) {
180 assign := ifStmt.Init.(*ast.AssignStmt)
181 if call, ok := assign.Rhs[0].(*ast.CallExpr); ok && assign.Tok == token.DEFINE {
182 lhs := assign.Lhs[0]
183 obj := typeutil.Callee(info, call)
184
185 if obj == nil ||
186 obj != stringsTrimPrefix && obj != bytesTrimPrefix && obj != stringsTrimSuffix && obj != bytesTrimSuffix {
187 continue
188 }
189
190 isPrefix1 := strings.HasSuffix(obj.Name(), "Prefix")
191 var cutFuncName, message, fixMessage string
192 if isPrefix1 {
193 cutFuncName = "CutPrefix"
194 message = "TrimPrefix can be simplified to CutPrefix"
195 fixMessage = "Replace TrimPrefix with CutPrefix"
196 } else {
197 cutFuncName = "CutSuffix"
198 message = "TrimSuffix can be simplified to CutSuffix"
199 fixMessage = "Replace TrimSuffix with CutSuffix"
200 }
201
202 if astutil.EqualSyntax(lhs, bin.X) && astutil.EqualSyntax(call.Args[0], bin.Y) ||
203 (astutil.EqualSyntax(lhs, bin.Y) && astutil.EqualSyntax(call.Args[0], bin.X)) {
204
205 okVarName := refactor.FreshName(info.Scopes[ifStmt], ifStmt.Pos(), "ok")
206
207
208
209
210
211
212 prefix, importEdits := refactor.AddImport(
213 info,
214 curFile.Node().(*ast.File),
215 obj.Pkg().Name(),
216 obj.Pkg().Path(),
217 cutFuncName,
218 call.Pos(),
219 )
220
221 pass.Report(analysis.Diagnostic{
222
223 Pos: ifStmt.Init.Pos(),
224 End: ifStmt.Cond.End(),
225 Message: message,
226 SuggestedFixes: []analysis.SuggestedFix{{
227 Message: fixMessage,
228
229
230
231
232 TextEdits: append(importEdits, []analysis.TextEdit{
233 {
234 Pos: assign.Lhs[0].End(),
235 End: assign.Lhs[0].End(),
236 NewText: fmt.Appendf(nil, ", %s", okVarName),
237 },
238 {
239 Pos: call.Fun.Pos(),
240 End: call.Fun.End(),
241 NewText: fmt.Appendf(nil, "%s%s", prefix, cutFuncName),
242 },
243 {
244 Pos: ifStmt.Cond.Pos(),
245 End: ifStmt.Cond.End(),
246 NewText: []byte(okVarName),
247 },
248 }...),
249 }},
250 })
251 }
252 }
253 }
254 }
255 }
256 return nil, nil
257 }
258
View as plain text