1package main 2 3import ( 4 "go/ast" 5 "go/types" 6 "strings" 7 8 "golang.org/x/tools/go/analysis" 9 "golang.org/x/tools/go/analysis/passes/inspect" 10 "golang.org/x/tools/go/analysis/singlechecker" 11) 12 13func main() { 14 singlechecker.Main(Analyzer) 15} 16 17var Analyzer = &analysis.Analyzer{ 18 Name: "hclogvet", 19 Doc: "check hclog invocations", 20 Requires: []*analysis.Analyzer{inspect.Analyzer}, 21 Run: run, 22} 23 24var checkHCLogFunc = map[string]bool{ 25 "Trace": true, 26 "Debug": true, 27 "Info": true, 28 "Warn": true, 29 "Error": true, 30} 31 32func run(pass *analysis.Pass) (interface{}, error) { 33 for _, f := range pass.Files { 34 ast.Inspect(f, func(n ast.Node) bool { 35 call, ok := n.(*ast.CallExpr) 36 if !ok { 37 return true 38 } 39 40 fun, ok := call.Fun.(*ast.SelectorExpr) 41 if !ok { 42 return true 43 } 44 45 typ := pass.TypesInfo.Types[fun] 46 sig, ok := typ.Type.(*types.Signature) 47 if !ok { 48 return true 49 } else if sig == nil { 50 return true // the call is not on of the form x.f() 51 } 52 53 recv := pass.TypesInfo.Types[fun.X] 54 if recv.Type == nil { 55 return true 56 } 57 58 if !isNamedType(recv.Type, "github.com/hashicorp/go-hclog", "Logger") && 59 !isNamedType(recv.Type, "github.com/hashicorp/go-hclog", "InterceptLogger") { 60 return true 61 } 62 63 if _, ok := checkHCLogFunc[fun.Sel.Name]; !ok { 64 return true 65 } 66 67 // arity should be odd, with the log message being first and then followed by K/V pairs 68 if numArgs := len(call.Args); numArgs%2 != 1 { 69 pairs := numArgs / 2 70 noun := "pairs" 71 if pairs == 1 { 72 noun = "pair" 73 } 74 pass.Reportf(call.Lparen, "invalid number of log arguments to %s (%d valid %s only)", fun.Sel.Name, pairs, noun) 75 } 76 77 return true 78 }) 79 } 80 81 return nil, nil 82} 83 84// isNamedType reports whether t is the named type path.name. 85func isNamedType(t types.Type, path, name string) bool { 86 n, ok := t.(*types.Named) 87 if !ok { 88 return false 89 } 90 obj := n.Obj() 91 return obj.Name() == name && isPackage(obj.Pkg(), path) 92} 93 94// isPackage reports whether pkg has path as the canonical path, 95// taking into account vendoring effects 96func isPackage(pkg *types.Package, path string) bool { 97 if pkg == nil { 98 return false 99 } 100 101 return pkg.Path() == path || 102 strings.HasSuffix(pkg.Path(), "/vendor/"+path) 103} 104