golang.org/x/tools@v0.21.0/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 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 //go:embed doc.go 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 // describe returns a string describing the type typ contained within the type 34 // set of inType. If non-empty, inName is used as the name of inType (this is 35 // necessary so that we can use alias type names that may not be reachable from 36 // inType itself). 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 // TODO(adonovan): don't discard alias type, return its name. 65 if v, _ := typ.(*types.Basic); v != nil { 66 return v.Name() 67 } 68 if v, _ := typ.(interface{ Obj() *types.TypeName }); v != nil { // Named, TypeParam 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 // Retrieve target type name. 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 // In the conversion T(v) of a value v of type V to a target type T, we 100 // look for types T0 in the type set of T and V0 in the type set of V, such 101 // that V0->T0 is a problematic conversion. If T and V are not type 102 // parameters, this amounts to just checking if V->T is a problematic 103 // conversion. 104 105 // First, find a type T0 in T that has an underlying type of string. 106 T := tname.Type() 107 ttypes, err := structuralTypes(T) 108 if err != nil { 109 return // invalid type 110 } 111 112 var T0 types.Type // string type in the type set of T 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 // No target types have an underlying type of string. 124 return 125 } 126 127 // Next, find a type V0 in V that has an underlying integral type that is 128 // not byte or rune. 129 V := pass.TypesInfo.TypeOf(arg) 130 vtypes, err := structuralTypes(V) 131 if err != nil { 132 return // invalid type 133 } 134 135 var V0 types.Type // integral type in the type set of V 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 // No source types are non-byte or rune integer types. 151 return 152 } 153 154 convertibleToRune := true // if true, we can suggest a fix 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 // something went wrong 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 }