1
2
3
4
5 package modernize
6
7 import (
8 "fmt"
9 "go/ast"
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/edge"
16 "golang.org/x/tools/go/types/typeutil"
17 "golang.org/x/tools/internal/analysis/analyzerutil"
18 typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
19 "golang.org/x/tools/internal/typesinternal/typeindex"
20 "golang.org/x/tools/internal/versions"
21 )
22
23 var StringsSeqAnalyzer = &analysis.Analyzer{
24 Name: "stringsseq",
25 Doc: analyzerutil.MustExtractDoc(doc, "stringsseq"),
26 Requires: []*analysis.Analyzer{
27 inspect.Analyzer,
28 typeindexanalyzer.Analyzer,
29 },
30 Run: stringsseq,
31 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stringsseq",
32 }
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 func stringsseq(pass *analysis.Pass) (any, error) {
49 var (
50 index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
51 info = pass.TypesInfo
52
53 stringsSplit = index.Object("strings", "Split")
54 stringsFields = index.Object("strings", "Fields")
55 bytesSplit = index.Object("bytes", "Split")
56 bytesFields = index.Object("bytes", "Fields")
57 )
58 if !index.Used(stringsSplit, stringsFields, bytesSplit, bytesFields) {
59 return nil, nil
60 }
61
62 for curFile := range filesUsingGoVersion(pass, versions.Go1_24) {
63 for curRange := range curFile.Preorder((*ast.RangeStmt)(nil)) {
64 rng := curRange.Node().(*ast.RangeStmt)
65
66
67
68 if id, ok := rng.Key.(*ast.Ident); ok && id.Name != "_" {
69 continue
70 }
71
72
73
74 call, ok := rng.X.(*ast.CallExpr)
75 if !ok {
76 if id, ok := rng.X.(*ast.Ident); ok {
77 if v, ok := info.Uses[id].(*types.Var); ok {
78 if ek, idx := curRange.ParentEdge(); ek == edge.BlockStmt_List && idx > 0 {
79 curPrev, _ := curRange.PrevSibling()
80 if assign, ok := curPrev.Node().(*ast.AssignStmt); ok &&
81 assign.Tok == token.DEFINE &&
82 len(assign.Lhs) == 1 &&
83 len(assign.Rhs) == 1 &&
84 info.Defs[assign.Lhs[0].(*ast.Ident)] == v &&
85 soleUseIs(index, v, id) {
86
87
88
89
90 call, _ = assign.Rhs[0].(*ast.CallExpr)
91 }
92 }
93 }
94 }
95 }
96
97 if call != nil {
98 var edits []analysis.TextEdit
99 if rng.Key != nil {
100
101
102
103
104 end := rng.Range
105 if rng.Value != nil {
106 end = rng.Value.Pos()
107 }
108 edits = append(edits, analysis.TextEdit{
109 Pos: rng.Key.Pos(),
110 End: end,
111 })
112 }
113
114 sel, ok := call.Fun.(*ast.SelectorExpr)
115 if !ok {
116 continue
117 }
118
119 switch obj := typeutil.Callee(info, call); obj {
120 case stringsSplit, stringsFields, bytesSplit, bytesFields:
121 oldFnName := obj.Name()
122 seqFnName := fmt.Sprintf("%sSeq", oldFnName)
123 pass.Report(analysis.Diagnostic{
124 Pos: sel.Pos(),
125 End: sel.End(),
126 Message: fmt.Sprintf("Ranging over %s is more efficient", seqFnName),
127 SuggestedFixes: []analysis.SuggestedFix{{
128 Message: fmt.Sprintf("Replace %s with %s", oldFnName, seqFnName),
129 TextEdits: append(edits, analysis.TextEdit{
130 Pos: sel.Sel.Pos(),
131 End: sel.Sel.End(),
132 NewText: []byte(seqFnName)}),
133 }},
134 })
135 }
136 }
137 }
138 }
139 return nil, nil
140 }
141
View as plain text