GoPLS Viewer

Home|gopls/go/analysis/passes/stringintconv/string.go
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.
7package stringintconv
8
9import (
10    "fmt"
11    "go/ast"
12    "go/types"
13    "strings"
14
15    "golang.org/x/tools/go/analysis"
16    "golang.org/x/tools/go/analysis/passes/inspect"
17    "golang.org/x/tools/go/ast/inspector"
18    "golang.org/x/tools/internal/typeparams"
19)
20
21const Doc = `check for string(int) conversions
22
23This 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
25return the UTF-8 representation of the Unicode code point x, and not a decimal
26string representation of x as one might expect. Furthermore, if x denotes an
27invalid code point, the conversion cannot be statically rejected.
28
29For conversions that intend on using the code point, consider replacing them
30with string(rune(x)). Otherwise, strconv.Itoa and its equivalents return the
31string representation of the value in the desired base.
32`
33
34var 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).
45func describe(typinType types.TypeinName stringstring {
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(parentheticalsunderName)
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
70func typeName(typ types.Typestring {
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
80func 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        ttypeserr := 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        vtypeserr := 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.Bytetypes.Runetypes.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(ttypes.Typ[types.Rune]) {
163                convertibleToRune = false
164                break
165            }
166        }
167
168        target := describe(T0Ttname.Name())
169        source := describe(V0VtypeName(V))
170
171        if target == "" || source == "" {
172            return // something went wrong
173        }
174
175        diag := analysis.Diagnostic{
176            Pos:     n.Pos(),
177            Messagefmt.Sprintf("conversion from %s to %s yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?)"sourcetarget),
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 nilnil
202}
203
204func structuralTypes(t types.Type) ([]types.Typeerror) {
205    var structuralTypes []types.Type
206    switch t := t.(type) {
207    case *typeparams.TypeParam:
208        termserr := typeparams.StructuralTerms(t)
209        if err != nil {
210            return nilerr
211        }
212        for _term := range terms {
213            structuralTypes = append(structuralTypesterm.Type())
214        }
215    default:
216        structuralTypes = append(structuralTypest)
217    }
218    return structuralTypesnil
219}
220
MembersX
describe
describe.parentheticals
run.BlockStmt.T
run.BlockStmt.err
structuralTypes.BlockStmt.terms
fmt
describe.inType
describe.inName
describe.underName
run.BlockStmt.vtypes
run.BlockStmt.convertibleToRune
run.BlockStmt.target
run.BlockStmt.diag
structuralTypes.BlockStmt.RangeStmt_5572.term
types
structuralTypes
typeName
typeName.typ
run
run.nodeFilter
run.BlockStmt.RangeStmt_4387.t
ast
run.pass
run.BlockStmt.tname
run.BlockStmt.source
structuralTypes.structuralTypes
strings
inspect
inspector
Doc
run.BlockStmt.T0
run.BlockStmt.V
run.BlockStmt.RangeStmt_3988.vt
structuralTypes.BlockStmt.err
analysis
typeparams
run.BlockStmt.RangeStmt_3473.tt
describe.typ
describe.name
run.BlockStmt.ttypes
run.BlockStmt.V0
structuralTypes.t
Members
X