1// Package code answers structural and type questions about Go code. 2package code 3 4import ( 5 "flag" 6 "fmt" 7 "go/ast" 8 "go/constant" 9 "go/token" 10 "go/types" 11 "strings" 12 13 "honnef.co/go/tools/analysis/facts" 14 "honnef.co/go/tools/go/types/typeutil" 15 16 "golang.org/x/tools/go/analysis" 17 "golang.org/x/tools/go/ast/astutil" 18) 19 20type Positioner interface { 21 Pos() token.Pos 22} 23 24func IsOfType(pass *analysis.Pass, expr ast.Expr, name string) bool { 25 return typeutil.IsType(pass.TypesInfo.TypeOf(expr), name) 26} 27 28func IsInTest(pass *analysis.Pass, node Positioner) bool { 29 // FIXME(dh): this doesn't work for global variables with 30 // initializers 31 f := pass.Fset.File(node.Pos()) 32 return f != nil && strings.HasSuffix(f.Name(), "_test.go") 33} 34 35// IsMain reports whether the package being processed is a package 36// main. 37func IsMain(pass *analysis.Pass) bool { 38 return pass.Pkg.Name() == "main" 39} 40 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. 45// 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. 50func IsMainLike(pass *analysis.Pass) bool { 51 if pass.Pkg.Name() == "main" { 52 return true 53 } 54 for _, imp := range pass.Pkg.Imports() { 55 if imp.Path() == "github.com/spf13/cobra" { 56 return true 57 } 58 } 59 return false 60} 61 62func SelectorName(pass *analysis.Pass, expr *ast.SelectorExpr) string { 63 info := pass.TypesInfo 64 sel := info.Selections[expr] 65 if sel == nil { 66 if x, ok := expr.X.(*ast.Ident); ok { 67 pkg, ok := info.ObjectOf(x).(*types.PkgName) 68 if !ok { 69 // This shouldn't happen 70 return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name) 71 } 72 return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name) 73 } 74 panic(fmt.Sprintf("unsupported selector: %v", expr)) 75 } 76 return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name()) 77} 78 79func IsNil(pass *analysis.Pass, expr ast.Expr) bool { 80 return pass.TypesInfo.Types[expr].IsNil() 81} 82 83func BoolConst(pass *analysis.Pass, expr ast.Expr) bool { 84 val := pass.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val() 85 return constant.BoolVal(val) 86} 87 88func 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. 92 93 ident, ok := expr.(*ast.Ident) 94 if !ok { 95 return false 96 } 97 obj := pass.TypesInfo.ObjectOf(ident) 98 c, ok := obj.(*types.Const) 99 if !ok { 100 return false 101 } 102 basic, ok := c.Type().(*types.Basic) 103 if !ok { 104 return false 105 } 106 if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool { 107 return false 108 } 109 return true 110} 111 112func ExprToInt(pass *analysis.Pass, expr ast.Expr) (int64, bool) { 113 tv := pass.TypesInfo.Types[expr] 114 if tv.Value == nil { 115 return 0, false 116 } 117 if tv.Value.Kind() != constant.Int { 118 return 0, false 119 } 120 return constant.Int64Val(tv.Value) 121} 122 123func ExprToString(pass *analysis.Pass, expr ast.Expr) (string, bool) { 124 val := pass.TypesInfo.Types[expr].Value 125 if val == nil { 126 return "", false 127 } 128 if val.Kind() != constant.String { 129 return "", false 130 } 131 return constant.StringVal(val), true 132} 133 134func 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) 138 if !ok { 139 return "" 140 } 141 return typeutil.FuncName(fn) 142 case *ast.Ident: 143 obj := pass.TypesInfo.ObjectOf(fun) 144 switch obj := obj.(type) { 145 case *types.Func: 146 return typeutil.FuncName(obj) 147 case *types.Builtin: 148 return obj.Name() 149 default: 150 return "" 151 } 152 default: 153 return "" 154 } 155} 156 157func IsCallTo(pass *analysis.Pass, node ast.Node, name string) bool { 158 call, ok := node.(*ast.CallExpr) 159 if !ok { 160 return false 161 } 162 return CallName(pass, call) == name 163} 164 165func IsCallToAny(pass *analysis.Pass, node ast.Node, names ...string) bool { 166 call, ok := node.(*ast.CallExpr) 167 if !ok { 168 return false 169 } 170 q := CallName(pass, call) 171 for _, name := range names { 172 if q == name { 173 return true 174 } 175 } 176 return false 177} 178 179func 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())] 182} 183 184// IsGenerated reports whether pos is in a generated file, It ignores 185// //line directives. 186func IsGenerated(pass *analysis.Pass, pos token.Pos) bool { 187 _, ok := Generator(pass, pos) 188 return ok 189} 190 191// Generator returns the generator that generated the file containing 192// pos. It ignores //line directives. 193func 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) 196 g, ok := m[file] 197 return g, ok 198} 199 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. 205func MayHaveSideEffects(pass *analysis.Pass, expr ast.Expr, purity facts.PurityResult) bool { 206 switch expr := expr.(type) { 207 case *ast.BadExpr: 208 return true 209 case *ast.Ellipsis: 210 return MayHaveSideEffects(pass, expr.Elt, purity) 211 case *ast.FuncLit: 212 // the literal itself cannot have side ffects, only calling it 213 // might, which is handled by CallExpr. 214 return false 215 case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType: 216 // types cannot have side effects 217 return false 218 case *ast.BasicLit: 219 return false 220 case *ast.BinaryExpr: 221 return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Y, purity) 222 case *ast.CallExpr: 223 if purity == nil { 224 return true 225 } 226 switch obj := typeutil.Callee(pass.TypesInfo, expr).(type) { 227 case *types.Func: 228 if _, ok := purity[obj]; !ok { 229 return true 230 } 231 case *types.Builtin: 232 switch obj.Name() { 233 case "len", "cap": 234 default: 235 return true 236 } 237 default: 238 return true 239 } 240 for _, arg := range expr.Args { 241 if MayHaveSideEffects(pass, arg, purity) { 242 return true 243 } 244 } 245 return false 246 case *ast.CompositeLit: 247 if MayHaveSideEffects(pass, expr.Type, purity) { 248 return true 249 } 250 for _, elt := range expr.Elts { 251 if MayHaveSideEffects(pass, elt, purity) { 252 return true 253 } 254 } 255 return false 256 case *ast.Ident: 257 return false 258 case *ast.IndexExpr: 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) 264 case *ast.SliceExpr: 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) 269 case *ast.StarExpr: 270 return MayHaveSideEffects(pass, expr.X, purity) 271 case *ast.TypeAssertExpr: 272 return MayHaveSideEffects(pass, expr.X, purity) 273 case *ast.UnaryExpr: 274 if MayHaveSideEffects(pass, expr.X, purity) { 275 return true 276 } 277 return expr.Op == token.ARROW 278 case *ast.ParenExpr: 279 return MayHaveSideEffects(pass, expr.X, purity) 280 case nil: 281 return false 282 default: 283 panic(fmt.Sprintf("internal error: unhandled type %T", expr)) 284 } 285} 286 287func IsGoVersion(pass *analysis.Pass, minor int) bool { 288 f, ok := pass.Analyzer.Flags.Lookup("go").Value.(flag.Getter) 289 if !ok { 290 panic("requested Go version, but analyzer has no version flag") 291 } 292 version := f.Get().(int) 293 return version >= minor 294} 295