1
2
3
4
5 package stringintconv
6
7 import (
8 _ "embed"
9 "fmt"
10 "go/ast"
11 "go/types"
12 "strings"
13
14 "golang.org/x/tools/go/analysis"
15 "golang.org/x/tools/go/analysis/passes/inspect"
16 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
17 "golang.org/x/tools/go/ast/inspector"
18 "golang.org/x/tools/internal/typeparams"
19 )
20
21
22 var doc string
23
24 var Analyzer = &analysis.Analyzer{
25 Name: "stringintconv",
26 Doc: analysisutil.MustExtractDoc(doc, "stringintconv"),
27 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stringintconv",
28 Requires: []*analysis.Analyzer{inspect.Analyzer},
29 Run: run,
30 }
31
32
33
34
35
36 func describe(typ, inType types.Type, inName string) string {
37 name := inName
38 if typ != inType {
39 name = typeName(typ)
40 }
41 if name == "" {
42 return ""
43 }
44
45 var parentheticals []string
46 if underName := typeName(typ.Underlying()); underName != "" && underName != name {
47 parentheticals = append(parentheticals, underName)
48 }
49
50 if typ != inType && inName != "" && inName != name {
51 parentheticals = append(parentheticals, "in "+inName)
52 }
53
54 if len(parentheticals) > 0 {
55 name += " (" + strings.Join(parentheticals, ", ") + ")"
56 }
57
58 return name
59 }
60
61 func typeName(typ types.Type) string {
62 if v, _ := typ.(interface{ Name() string }); v != nil {
63 return v.Name()
64 }
65 if v, _ := typ.(interface{ Obj() *types.TypeName }); v != nil {
66 return v.Obj().Name()
67 }
68 return ""
69 }
70
71 func run(pass *analysis.Pass) (interface{}, error) {
72 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
73 nodeFilter := []ast.Node{
74 (*ast.CallExpr)(nil),
75 }
76 inspect.Preorder(nodeFilter, func(n ast.Node) {
77 call := n.(*ast.CallExpr)
78
79 if len(call.Args) != 1 {
80 return
81 }
82 arg := call.Args[0]
83
84
85 var tname *types.TypeName
86 switch fun := call.Fun.(type) {
87 case *ast.Ident:
88 tname, _ = pass.TypesInfo.Uses[fun].(*types.TypeName)
89 case *ast.SelectorExpr:
90 tname, _ = pass.TypesInfo.Uses[fun.Sel].(*types.TypeName)
91 }
92 if tname == nil {
93 return
94 }
95
96
97
98
99
100
101
102
103 T := tname.Type()
104 ttypes, err := structuralTypes(T)
105 if err != nil {
106 return
107 }
108
109 var T0 types.Type
110
111 for _, tt := range ttypes {
112 u, _ := tt.Underlying().(*types.Basic)
113 if u != nil && u.Kind() == types.String {
114 T0 = tt
115 break
116 }
117 }
118
119 if T0 == nil {
120
121 return
122 }
123
124
125
126 V := pass.TypesInfo.TypeOf(arg)
127 vtypes, err := structuralTypes(V)
128 if err != nil {
129 return
130 }
131
132 var V0 types.Type
133
134 for _, vt := range vtypes {
135 u, _ := vt.Underlying().(*types.Basic)
136 if u != nil && u.Info()&types.IsInteger != 0 {
137 switch u.Kind() {
138 case types.Byte, types.Rune, types.UntypedRune:
139 continue
140 }
141 V0 = vt
142 break
143 }
144 }
145
146 if V0 == nil {
147
148 return
149 }
150
151 convertibleToRune := true
152 for _, t := range vtypes {
153 if !types.ConvertibleTo(t, types.Typ[types.Rune]) {
154 convertibleToRune = false
155 break
156 }
157 }
158
159 target := describe(T0, T, tname.Name())
160 source := describe(V0, V, typeName(V))
161
162 if target == "" || source == "" {
163 return
164 }
165
166 diag := analysis.Diagnostic{
167 Pos: n.Pos(),
168 Message: fmt.Sprintf("conversion from %s to %s yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?)", source, target),
169 }
170
171 if convertibleToRune {
172 diag.SuggestedFixes = []analysis.SuggestedFix{
173 {
174 Message: "Did you mean to convert a rune to a string?",
175 TextEdits: []analysis.TextEdit{
176 {
177 Pos: arg.Pos(),
178 End: arg.Pos(),
179 NewText: []byte("rune("),
180 },
181 {
182 Pos: arg.End(),
183 End: arg.End(),
184 NewText: []byte(")"),
185 },
186 },
187 },
188 }
189 }
190 pass.Report(diag)
191 })
192 return nil, nil
193 }
194
195 func structuralTypes(t types.Type) ([]types.Type, error) {
196 var structuralTypes []types.Type
197 switch t := t.(type) {
198 case *types.TypeParam:
199 terms, err := typeparams.StructuralTerms(t)
200 if err != nil {
201 return nil, err
202 }
203 for _, term := range terms {
204 structuralTypes = append(structuralTypes, term.Type())
205 }
206 default:
207 structuralTypes = append(structuralTypes, t)
208 }
209 return structuralTypes, nil
210 }
211
View as plain text