forked from golang/tools
/
string.go
126 lines (113 loc) · 3.34 KB
/
string.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package stringintconv defines an Analyzer that flags type conversions
// from integers to strings.
package stringintconv
import (
"fmt"
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
const Doc = `check for string(int) conversions
This checker flags conversions of the form string(x) where x is an integer
(but not byte or rune) type. Such conversions are discouraged because they
return the UTF-8 representation of the Unicode code point x, and not a decimal
string representation of x as one might expect. Furthermore, if x denotes an
invalid code point, the conversion cannot be statically rejected.
For conversions that intend on using the code point, consider replacing them
with string(rune(x)). Otherwise, strconv.Itoa and its equivalents return the
string representation of the value in the desired base.
`
var Analyzer = &analysis.Analyzer{
Name: "stringintconv",
Doc: Doc,
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func typeName(typ types.Type) string {
if v, _ := typ.(interface{ Name() string }); v != nil {
return v.Name()
}
if v, _ := typ.(interface{ Obj() *types.TypeName }); v != nil {
return v.Obj().Name()
}
return ""
}
func run(pass *analysis.Pass) (interface{}, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
// Retrieve target type name.
var tname *types.TypeName
switch fun := call.Fun.(type) {
case *ast.Ident:
tname, _ = pass.TypesInfo.Uses[fun].(*types.TypeName)
case *ast.SelectorExpr:
tname, _ = pass.TypesInfo.Uses[fun.Sel].(*types.TypeName)
}
if tname == nil {
return
}
target := tname.Name()
// Check that target type T in T(v) has an underlying type of string.
T, _ := tname.Type().Underlying().(*types.Basic)
if T == nil || T.Kind() != types.String {
return
}
if s := T.Name(); target != s {
target += " (" + s + ")"
}
// Check that type V of v has an underlying integral type that is not byte or rune.
if len(call.Args) != 1 {
return
}
v := call.Args[0]
vtyp := pass.TypesInfo.TypeOf(v)
V, _ := vtyp.Underlying().(*types.Basic)
if V == nil || V.Info()&types.IsInteger == 0 {
return
}
switch V.Kind() {
case types.Byte, types.Rune, types.UntypedRune:
return
}
// Retrieve source type name.
source := typeName(vtyp)
if source == "" {
return
}
if s := V.Name(); source != s {
source += " (" + s + ")"
}
diag := analysis.Diagnostic{
Pos: n.Pos(),
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),
SuggestedFixes: []analysis.SuggestedFix{
{
Message: "Did you mean to convert a rune to a string?",
TextEdits: []analysis.TextEdit{
{
Pos: v.Pos(),
End: v.Pos(),
NewText: []byte("rune("),
},
{
Pos: v.End(),
End: v.End(),
NewText: []byte(")"),
},
},
},
},
}
pass.Report(diag)
})
return nil, nil
}