1 // Package code answers structural and type questions about Go code.
13 "golang.org/x/tools/go/analysis"
14 "golang.org/x/tools/go/analysis/passes/inspect"
15 "golang.org/x/tools/go/ast/astutil"
16 "golang.org/x/tools/go/ast/inspector"
17 "honnef.co/go/tools/facts"
18 "honnef.co/go/tools/go/types/typeutil"
19 "honnef.co/go/tools/ir"
20 "honnef.co/go/tools/lint"
23 type Positioner interface {
27 func CallName(call *ir.CallCommon) string {
31 switch v := call.Value.(type) {
33 fn, ok := v.Object().(*types.Func)
37 return lint.FuncName(fn)
44 func IsCallTo(call *ir.CallCommon, name string) bool { return CallName(call) == name }
46 func IsCallToAny(call *ir.CallCommon, names ...string) bool {
48 for _, name := range names {
56 func IsType(T types.Type, name string) bool { return types.TypeString(T, nil) == name }
58 func FilterDebug(instr []ir.Instruction) []ir.Instruction {
59 var out []ir.Instruction
60 for _, ins := range instr {
61 if _, ok := ins.(*ir.DebugRef); !ok {
62 out = append(out, ins)
68 func IsExample(fn *ir.Function) bool {
69 if !strings.HasPrefix(fn.Name(), "Example") {
72 f := fn.Prog.Fset.File(fn.Pos())
76 return strings.HasSuffix(f.Name(), "_test.go")
79 func IsPointerLike(T types.Type) bool {
80 switch T := T.Underlying().(type) {
81 case *types.Interface, *types.Chan, *types.Map, *types.Signature, *types.Pointer:
84 return T.Kind() == types.UnsafePointer
89 func IsIdent(expr ast.Expr, ident string) bool {
90 id, ok := expr.(*ast.Ident)
91 return ok && id.Name == ident
94 // isBlank returns whether id is the blank identifier "_".
95 // If id == nil, the answer is false.
96 func IsBlank(id ast.Expr) bool {
97 ident, _ := id.(*ast.Ident)
98 return ident != nil && ident.Name == "_"
101 func IsIntLiteral(expr ast.Expr, literal string) bool {
102 lit, ok := expr.(*ast.BasicLit)
103 return ok && lit.Kind == token.INT && lit.Value == literal
106 // Deprecated: use IsIntLiteral instead
107 func IsZero(expr ast.Expr) bool {
108 return IsIntLiteral(expr, "0")
111 func IsOfType(pass *analysis.Pass, expr ast.Expr, name string) bool {
112 return IsType(pass.TypesInfo.TypeOf(expr), name)
115 func IsInTest(pass *analysis.Pass, node Positioner) bool {
116 // FIXME(dh): this doesn't work for global variables with
118 f := pass.Fset.File(node.Pos())
119 return f != nil && strings.HasSuffix(f.Name(), "_test.go")
122 // IsMain reports whether the package being processed is a package
124 func IsMain(pass *analysis.Pass) bool {
125 return pass.Pkg.Name() == "main"
128 // IsMainLike reports whether the package being processed is a
129 // main-like package. A main-like package is a package that is
130 // package main, or that is intended to be used by a tool framework
131 // such as cobra to implement a command.
133 // Note that this function errs on the side of false positives; it may
134 // return true for packages that aren't main-like. IsMainLike is
135 // intended for analyses that wish to suppress diagnostics for
136 // main-like packages to avoid false positives.
137 func IsMainLike(pass *analysis.Pass) bool {
138 if pass.Pkg.Name() == "main" {
141 for _, imp := range pass.Pkg.Imports() {
142 if imp.Path() == "github.com/spf13/cobra" {
149 func SelectorName(pass *analysis.Pass, expr *ast.SelectorExpr) string {
150 info := pass.TypesInfo
151 sel := info.Selections[expr]
153 if x, ok := expr.X.(*ast.Ident); ok {
154 pkg, ok := info.ObjectOf(x).(*types.PkgName)
156 // This shouldn't happen
157 return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name)
159 return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name)
161 panic(fmt.Sprintf("unsupported selector: %v", expr))
163 return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name())
166 func IsNil(pass *analysis.Pass, expr ast.Expr) bool {
167 return pass.TypesInfo.Types[expr].IsNil()
170 func BoolConst(pass *analysis.Pass, expr ast.Expr) bool {
171 val := pass.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
172 return constant.BoolVal(val)
175 func IsBoolConst(pass *analysis.Pass, expr ast.Expr) bool {
176 // We explicitly don't support typed bools because more often than
177 // not, custom bool types are used as binary enums and the
178 // explicit comparison is desired.
180 ident, ok := expr.(*ast.Ident)
184 obj := pass.TypesInfo.ObjectOf(ident)
185 c, ok := obj.(*types.Const)
189 basic, ok := c.Type().(*types.Basic)
193 if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool {
199 func ExprToInt(pass *analysis.Pass, expr ast.Expr) (int64, bool) {
200 tv := pass.TypesInfo.Types[expr]
204 if tv.Value.Kind() != constant.Int {
207 return constant.Int64Val(tv.Value)
210 func ExprToString(pass *analysis.Pass, expr ast.Expr) (string, bool) {
211 val := pass.TypesInfo.Types[expr].Value
215 if val.Kind() != constant.String {
218 return constant.StringVal(val), true
221 // Dereference returns a pointer's element type; otherwise it returns
223 func Dereference(T types.Type) types.Type {
224 if p, ok := T.Underlying().(*types.Pointer); ok {
230 // DereferenceR returns a pointer's element type; otherwise it returns
231 // T. If the element type is itself a pointer, DereferenceR will be
232 // applied recursively.
233 func DereferenceR(T types.Type) types.Type {
234 if p, ok := T.Underlying().(*types.Pointer); ok {
235 return DereferenceR(p.Elem())
240 func CallNameAST(pass *analysis.Pass, call *ast.CallExpr) string {
241 switch fun := astutil.Unparen(call.Fun).(type) {
242 case *ast.SelectorExpr:
243 fn, ok := pass.TypesInfo.ObjectOf(fun.Sel).(*types.Func)
247 return lint.FuncName(fn)
249 obj := pass.TypesInfo.ObjectOf(fun)
250 switch obj := obj.(type) {
252 return lint.FuncName(obj)
263 func IsCallToAST(pass *analysis.Pass, node ast.Node, name string) bool {
264 call, ok := node.(*ast.CallExpr)
268 return CallNameAST(pass, call) == name
271 func IsCallToAnyAST(pass *analysis.Pass, node ast.Node, names ...string) bool {
272 call, ok := node.(*ast.CallExpr)
276 q := CallNameAST(pass, call)
277 for _, name := range names {
285 func Preamble(f *ast.File) string {
291 for _, cmt := range f.Comments {
292 if cmt.Pos() >= cutoff {
295 out = append(out, cmt.Text())
297 return strings.Join(out, "\n")
300 func GroupSpecs(fset *token.FileSet, specs []ast.Spec) [][]ast.Spec {
304 groups := make([][]ast.Spec, 1)
305 groups[0] = append(groups[0], specs[0])
307 for _, spec := range specs[1:] {
308 g := groups[len(groups)-1]
309 if fset.PositionFor(spec.Pos(), false).Line-1 !=
310 fset.PositionFor(g[len(g)-1].End(), false).Line {
312 groups = append(groups, nil)
315 groups[len(groups)-1] = append(groups[len(groups)-1], spec)
321 func IsObject(obj types.Object, name string) bool {
323 if pkg := obj.Pkg(); pkg != nil {
324 path = pkg.Path() + "."
326 return path+obj.Name() == name
335 // FlattenFields recursively flattens T and embedded structs,
336 // returning a list of fields. If multiple fields with the same name
337 // exist, all will be returned.
338 func FlattenFields(T *types.Struct) []Field {
339 return flattenFields(T, nil, nil)
342 func flattenFields(T *types.Struct, path []int, seen map[types.Type]bool) []Field {
344 seen = map[types.Type]bool{}
351 for i := 0; i < T.NumFields(); i++ {
354 np := append(path[:len(path):len(path)], i)
355 if field.Anonymous() {
356 if s, ok := Dereference(field.Type()).Underlying().(*types.Struct); ok {
357 out = append(out, flattenFields(s, np, seen)...)
360 out = append(out, Field{field, tag, np})
366 func File(pass *analysis.Pass, node Positioner) *ast.File {
367 m := pass.ResultOf[facts.TokenFile].(map[*token.File]*ast.File)
368 return m[pass.Fset.File(node.Pos())]
371 // IsGenerated reports whether pos is in a generated file, It ignores
372 // //line directives.
373 func IsGenerated(pass *analysis.Pass, pos token.Pos) bool {
374 _, ok := Generator(pass, pos)
378 // Generator returns the generator that generated the file containing
379 // pos. It ignores //line directives.
380 func Generator(pass *analysis.Pass, pos token.Pos) (facts.Generator, bool) {
381 file := pass.Fset.PositionFor(pos, false).Filename
382 m := pass.ResultOf[facts.Generated].(map[string]facts.Generator)
387 // MayHaveSideEffects reports whether expr may have side effects. If
388 // the purity argument is nil, this function implements a purely
389 // syntactic check, meaning that any function call may have side
390 // effects, regardless of the called function's body. Otherwise,
391 // purity will be consulted to determine the purity of function calls.
392 func MayHaveSideEffects(pass *analysis.Pass, expr ast.Expr, purity facts.PurityResult) bool {
393 switch expr := expr.(type) {
397 return MayHaveSideEffects(pass, expr.Elt, purity)
399 // the literal itself cannot have side ffects, only calling it
400 // might, which is handled by CallExpr.
402 case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:
403 // types cannot have side effects
407 case *ast.BinaryExpr:
408 return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Y, purity)
413 switch obj := typeutil.Callee(pass.TypesInfo, expr).(type) {
415 if _, ok := purity[obj]; !ok {
427 for _, arg := range expr.Args {
428 if MayHaveSideEffects(pass, arg, purity) {
433 case *ast.CompositeLit:
434 if MayHaveSideEffects(pass, expr.Type, purity) {
437 for _, elt := range expr.Elts {
438 if MayHaveSideEffects(pass, elt, purity) {
446 return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Index, purity)
447 case *ast.KeyValueExpr:
448 return MayHaveSideEffects(pass, expr.Key, purity) || MayHaveSideEffects(pass, expr.Value, purity)
449 case *ast.SelectorExpr:
450 return MayHaveSideEffects(pass, expr.X, purity)
452 return MayHaveSideEffects(pass, expr.X, purity) ||
453 MayHaveSideEffects(pass, expr.Low, purity) ||
454 MayHaveSideEffects(pass, expr.High, purity) ||
455 MayHaveSideEffects(pass, expr.Max, purity)
457 return MayHaveSideEffects(pass, expr.X, purity)
458 case *ast.TypeAssertExpr:
459 return MayHaveSideEffects(pass, expr.X, purity)
461 if MayHaveSideEffects(pass, expr.X, purity) {
464 return expr.Op == token.ARROW
466 return MayHaveSideEffects(pass, expr.X, purity)
470 panic(fmt.Sprintf("internal error: unhandled type %T", expr))
474 func IsGoVersion(pass *analysis.Pass, minor int) bool {
475 version := pass.Analyzer.Flags.Lookup("go").Value.(flag.Getter).Get().(int)
476 return version >= minor
479 func Preorder(pass *analysis.Pass, fn func(ast.Node), types ...ast.Node) {
480 pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Preorder(types, fn)