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