github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/analysis/passes/stringintconv/string.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package stringintconv defines an Analyzer that flags type conversions 6 // from integers to strings. 7 package stringintconv 8 9 import ( 10 "fmt" 11 "go/ast" 12 "go/types" 13 "strings" 14 15 "github.com/powerman/golang-tools/go/analysis" 16 "github.com/powerman/golang-tools/go/analysis/passes/inspect" 17 "github.com/powerman/golang-tools/go/ast/inspector" 18 "github.com/powerman/golang-tools/internal/typeparams" 19 ) 20 21 const Doc = `check for string(int) conversions 22 23 This checker flags conversions of the form string(x) where x is an integer 24 (but not byte or rune) type. Such conversions are discouraged because they 25 return the UTF-8 representation of the Unicode code point x, and not a decimal 26 string representation of x as one might expect. Furthermore, if x denotes an 27 invalid code point, the conversion cannot be statically rejected. 28 29 For conversions that intend on using the code point, consider replacing them 30 with string(rune(x)). Otherwise, strconv.Itoa and its equivalents return the 31 string representation of the value in the desired base. 32 ` 33 34 var Analyzer = &analysis.Analyzer{ 35 Name: "stringintconv", 36 Doc: Doc, 37 Requires: []*analysis.Analyzer{inspect.Analyzer}, 38 Run: run, 39 } 40 41 // describe returns a string describing the type typ contained within the type 42 // set of inType. If non-empty, inName is used as the name of inType (this is 43 // necessary so that we can use alias type names that may not be reachable from 44 // inType itself). 45 func describe(typ, inType types.Type, inName string) string { 46 name := inName 47 if typ != inType { 48 name = typeName(typ) 49 } 50 if name == "" { 51 return "" 52 } 53 54 var parentheticals []string 55 if underName := typeName(typ.Underlying()); underName != "" && underName != name { 56 parentheticals = append(parentheticals, underName) 57 } 58 59 if typ != inType && inName != "" && inName != name { 60 parentheticals = append(parentheticals, "in "+inName) 61 } 62 63 if len(parentheticals) > 0 { 64 name += " (" + strings.Join(parentheticals, ", ") + ")" 65 } 66 67 return name 68 } 69 70 func typeName(typ types.Type) string { 71 if v, _ := typ.(interface{ Name() string }); v != nil { 72 return v.Name() 73 } 74 if v, _ := typ.(interface{ Obj() *types.TypeName }); v != nil { 75 return v.Obj().Name() 76 } 77 return "" 78 } 79 80 func run(pass *analysis.Pass) (interface{}, error) { 81 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 82 nodeFilter := []ast.Node{ 83 (*ast.CallExpr)(nil), 84 } 85 inspect.Preorder(nodeFilter, func(n ast.Node) { 86 call := n.(*ast.CallExpr) 87 88 if len(call.Args) != 1 { 89 return 90 } 91 arg := call.Args[0] 92 93 // Retrieve target type name. 94 var tname *types.TypeName 95 switch fun := call.Fun.(type) { 96 case *ast.Ident: 97 tname, _ = pass.TypesInfo.Uses[fun].(*types.TypeName) 98 case *ast.SelectorExpr: 99 tname, _ = pass.TypesInfo.Uses[fun.Sel].(*types.TypeName) 100 } 101 if tname == nil { 102 return 103 } 104 105 // In the conversion T(v) of a value v of type V to a target type T, we 106 // look for types T0 in the type set of T and V0 in the type set of V, such 107 // that V0->T0 is a problematic conversion. If T and V are not type 108 // parameters, this amounts to just checking if V->T is a problematic 109 // conversion. 110 111 // First, find a type T0 in T that has an underlying type of string. 112 T := tname.Type() 113 ttypes, err := structuralTypes(T) 114 if err != nil { 115 return // invalid type 116 } 117 118 var T0 types.Type // string type in the type set of T 119 120 for _, tt := range ttypes { 121 u, _ := tt.Underlying().(*types.Basic) 122 if u != nil && u.Kind() == types.String { 123 T0 = tt 124 break 125 } 126 } 127 128 if T0 == nil { 129 // No target types have an underlying type of string. 130 return 131 } 132 133 // Next, find a type V0 in V that has an underlying integral type that is 134 // not byte or rune. 135 V := pass.TypesInfo.TypeOf(arg) 136 vtypes, err := structuralTypes(V) 137 if err != nil { 138 return // invalid type 139 } 140 141 var V0 types.Type // integral type in the type set of V 142 143 for _, vt := range vtypes { 144 u, _ := vt.Underlying().(*types.Basic) 145 if u != nil && u.Info()&types.IsInteger != 0 { 146 switch u.Kind() { 147 case types.Byte, types.Rune, types.UntypedRune: 148 continue 149 } 150 V0 = vt 151 break 152 } 153 } 154 155 if V0 == nil { 156 // No source types are non-byte or rune integer types. 157 return 158 } 159 160 convertibleToRune := true // if true, we can suggest a fix 161 for _, t := range vtypes { 162 if !types.ConvertibleTo(t, types.Typ[types.Rune]) { 163 convertibleToRune = false 164 break 165 } 166 } 167 168 target := describe(T0, T, tname.Name()) 169 source := describe(V0, V, typeName(V)) 170 171 if target == "" || source == "" { 172 return // something went wrong 173 } 174 175 diag := analysis.Diagnostic{ 176 Pos: n.Pos(), 177 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), 178 } 179 180 if convertibleToRune { 181 diag.SuggestedFixes = []analysis.SuggestedFix{ 182 { 183 Message: "Did you mean to convert a rune to a string?", 184 TextEdits: []analysis.TextEdit{ 185 { 186 Pos: arg.Pos(), 187 End: arg.Pos(), 188 NewText: []byte("rune("), 189 }, 190 { 191 Pos: arg.End(), 192 End: arg.End(), 193 NewText: []byte(")"), 194 }, 195 }, 196 }, 197 } 198 } 199 pass.Report(diag) 200 }) 201 return nil, nil 202 } 203 204 func structuralTypes(t types.Type) ([]types.Type, error) { 205 var structuralTypes []types.Type 206 switch t := t.(type) { 207 case *typeparams.TypeParam: 208 terms, err := typeparams.StructuralTerms(t) 209 if err != nil { 210 return nil, err 211 } 212 for _, term := range terms { 213 structuralTypes = append(structuralTypes, term.Type()) 214 } 215 default: 216 structuralTypes = append(structuralTypes, t) 217 } 218 return structuralTypes, nil 219 }