1 // Package code answers structural and type questions about Go code.
13 "honnef.co/go/tools/analysis/facts"
14 "honnef.co/go/tools/go/types/typeutil"
16 "golang.org/x/tools/go/analysis"
17 "golang.org/x/tools/go/ast/astutil"
20 type Positioner interface {
24 func IsOfType(pass *analysis.Pass, expr ast.Expr, name string) bool {
25 return typeutil.IsType(pass.TypesInfo.TypeOf(expr), name)
28 func IsInTest(pass *analysis.Pass, node Positioner) bool {
29 // FIXME(dh): this doesn't work for global variables with
31 f := pass.Fset.File(node.Pos())
32 return f != nil && strings.HasSuffix(f.Name(), "_test.go")
35 // IsMain reports whether the package being processed is a package
37 func IsMain(pass *analysis.Pass) bool {
38 return pass.Pkg.Name() == "main"
41 // IsMainLike reports whether the package being processed is a
42 // main-like package. A main-like package is a package that is
43 // package main, or that is intended to be used by a tool framework
44 // such as cobra to implement a command.
46 // Note that this function errs on the side of false positives; it may
47 // return true for packages that aren't main-like. IsMainLike is
48 // intended for analyses that wish to suppress diagnostics for
49 // main-like packages to avoid false positives.
50 func IsMainLike(pass *analysis.Pass) bool {
51 if pass.Pkg.Name() == "main" {
54 for _, imp := range pass.Pkg.Imports() {
55 if imp.Path() == "github.com/spf13/cobra" {
62 func SelectorName(pass *analysis.Pass, expr *ast.SelectorExpr) string {
63 info := pass.TypesInfo
64 sel := info.Selections[expr]
66 if x, ok := expr.X.(*ast.Ident); ok {
67 pkg, ok := info.ObjectOf(x).(*types.PkgName)
69 // This shouldn't happen
70 return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name)
72 return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name)
74 panic(fmt.Sprintf("unsupported selector: %v", expr))
76 return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name())
79 func IsNil(pass *analysis.Pass, expr ast.Expr) bool {
80 return pass.TypesInfo.Types[expr].IsNil()
83 func BoolConst(pass *analysis.Pass, expr ast.Expr) bool {
84 val := pass.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
85 return constant.BoolVal(val)
88 func IsBoolConst(pass *analysis.Pass, expr ast.Expr) bool {
89 // We explicitly don't support typed bools because more often than
90 // not, custom bool types are used as binary enums and the
91 // explicit comparison is desired.
93 ident, ok := expr.(*ast.Ident)
97 obj := pass.TypesInfo.ObjectOf(ident)
98 c, ok := obj.(*types.Const)
102 basic, ok := c.Type().(*types.Basic)
106 if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool {
112 func ExprToInt(pass *analysis.Pass, expr ast.Expr) (int64, bool) {
113 tv := pass.TypesInfo.Types[expr]
117 if tv.Value.Kind() != constant.Int {
120 return constant.Int64Val(tv.Value)
123 func ExprToString(pass *analysis.Pass, expr ast.Expr) (string, bool) {
124 val := pass.TypesInfo.Types[expr].Value
128 if val.Kind() != constant.String {
131 return constant.StringVal(val), true
134 func CallName(pass *analysis.Pass, call *ast.CallExpr) string {
135 switch fun := astutil.Unparen(call.Fun).(type) {
136 case *ast.SelectorExpr:
137 fn, ok := pass.TypesInfo.ObjectOf(fun.Sel).(*types.Func)
141 return typeutil.FuncName(fn)
143 obj := pass.TypesInfo.ObjectOf(fun)
144 switch obj := obj.(type) {
146 return typeutil.FuncName(obj)
157 func IsCallTo(pass *analysis.Pass, node ast.Node, name string) bool {
158 call, ok := node.(*ast.CallExpr)
162 return CallName(pass, call) == name
165 func IsCallToAny(pass *analysis.Pass, node ast.Node, names ...string) bool {
166 call, ok := node.(*ast.CallExpr)
170 q := CallName(pass, call)
171 for _, name := range names {
179 func File(pass *analysis.Pass, node Positioner) *ast.File {
180 m := pass.ResultOf[facts.TokenFile].(map[*token.File]*ast.File)
181 return m[pass.Fset.File(node.Pos())]
184 // IsGenerated reports whether pos is in a generated file, It ignores
185 // //line directives.
186 func IsGenerated(pass *analysis.Pass, pos token.Pos) bool {
187 _, ok := Generator(pass, pos)
191 // Generator returns the generator that generated the file containing
192 // pos. It ignores //line directives.
193 func Generator(pass *analysis.Pass, pos token.Pos) (facts.Generator, bool) {
194 file := pass.Fset.PositionFor(pos, false).Filename
195 m := pass.ResultOf[facts.Generated].(map[string]facts.Generator)
200 // MayHaveSideEffects reports whether expr may have side effects. If
201 // the purity argument is nil, this function implements a purely
202 // syntactic check, meaning that any function call may have side
203 // effects, regardless of the called function's body. Otherwise,
204 // purity will be consulted to determine the purity of function calls.
205 func MayHaveSideEffects(pass *analysis.Pass, expr ast.Expr, purity facts.PurityResult) bool {
206 switch expr := expr.(type) {
210 return MayHaveSideEffects(pass, expr.Elt, purity)
212 // the literal itself cannot have side ffects, only calling it
213 // might, which is handled by CallExpr.
215 case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:
216 // types cannot have side effects
220 case *ast.BinaryExpr:
221 return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Y, purity)
226 switch obj := typeutil.Callee(pass.TypesInfo, expr).(type) {
228 if _, ok := purity[obj]; !ok {
240 for _, arg := range expr.Args {
241 if MayHaveSideEffects(pass, arg, purity) {
246 case *ast.CompositeLit:
247 if MayHaveSideEffects(pass, expr.Type, purity) {
250 for _, elt := range expr.Elts {
251 if MayHaveSideEffects(pass, elt, purity) {
259 return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Index, purity)
260 case *ast.KeyValueExpr:
261 return MayHaveSideEffects(pass, expr.Key, purity) || MayHaveSideEffects(pass, expr.Value, purity)
262 case *ast.SelectorExpr:
263 return MayHaveSideEffects(pass, expr.X, purity)
265 return MayHaveSideEffects(pass, expr.X, purity) ||
266 MayHaveSideEffects(pass, expr.Low, purity) ||
267 MayHaveSideEffects(pass, expr.High, purity) ||
268 MayHaveSideEffects(pass, expr.Max, purity)
270 return MayHaveSideEffects(pass, expr.X, purity)
271 case *ast.TypeAssertExpr:
272 return MayHaveSideEffects(pass, expr.X, purity)
274 if MayHaveSideEffects(pass, expr.X, purity) {
277 return expr.Op == token.ARROW
279 return MayHaveSideEffects(pass, expr.X, purity)
283 panic(fmt.Sprintf("internal error: unhandled type %T", expr))
287 func IsGoVersion(pass *analysis.Pass, minor int) bool {
288 f, ok := pass.Analyzer.Flags.Lookup("go").Value.(flag.Getter)
290 panic("requested Go version, but analyzer has no version flag")
292 version := f.Get().(int)
293 return version >= minor