gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/opennota/check/cmd/varcheck/varcheck.go (about) 1 // This program is free software: you can redistribute it and/or modify 2 // it under the terms of the GNU General Public License as published by 3 // the Free Software Foundation, either version 3 of the License, or 4 // (at your option) any later version. 5 // 6 // This program is distributed in the hope that it will be useful, 7 // but WITHOUT ANY WARRANTY; without even the implied warranty of 8 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 // GNU General Public License for more details. 10 // 11 // You should have received a copy of the GNU General Public License 12 // along with this program. If not, see <http://www.gnu.org/licenses/>. 13 14 package main 15 16 import ( 17 "flag" 18 "fmt" 19 "go/ast" 20 "go/build" 21 "go/token" 22 "log" 23 "os" 24 "sort" 25 "strings" 26 27 "github.com/kisielk/gotool" 28 "golang.org/x/tools/go/loader" 29 "go/types" 30 ) 31 32 var ( 33 reportExported = flag.Bool("e", false, "Report exported variables and constants") 34 ) 35 36 type object struct { 37 pkgPath string 38 name string 39 } 40 41 type visitor struct { 42 prog *loader.Program 43 pkg *loader.PackageInfo 44 uses map[object]int 45 positions map[object]token.Position 46 insideFunc bool 47 } 48 49 func getKey(obj types.Object) object { 50 if obj == nil { 51 return object{} 52 } 53 54 pkg := obj.Pkg() 55 pkgPath := "" 56 if pkg != nil { 57 pkgPath = pkg.Path() 58 } 59 60 return object{ 61 pkgPath: pkgPath, 62 name: obj.Name(), 63 } 64 } 65 66 func (v *visitor) decl(obj types.Object) { 67 key := getKey(obj) 68 if _, ok := v.uses[key]; !ok { 69 v.uses[key] = 0 70 } 71 if _, ok := v.positions[key]; !ok { 72 v.positions[key] = v.prog.Fset.Position(obj.Pos()) 73 } 74 } 75 76 func (v *visitor) use(obj types.Object) { 77 key := getKey(obj) 78 if _, ok := v.uses[key]; ok { 79 v.uses[key]++ 80 } else { 81 v.uses[key] = 1 82 } 83 } 84 85 func isReserved(name string) bool { 86 return name == "_" || strings.HasPrefix(strings.ToLower(name), "_cgo_") 87 } 88 89 func (v *visitor) Visit(node ast.Node) ast.Visitor { 90 switch node := node.(type) { 91 case *ast.Ident: 92 v.use(v.pkg.Info.Uses[node]) 93 94 case *ast.ValueSpec: 95 if !v.insideFunc { 96 for _, ident := range node.Names { 97 if !isReserved(ident.Name) { 98 v.decl(v.pkg.Info.Defs[ident]) 99 } 100 } 101 } 102 for _, val := range node.Values { 103 ast.Walk(v, val) 104 } 105 if node.Type != nil { 106 ast.Walk(v, node.Type) 107 } 108 return nil 109 110 case *ast.FuncDecl: 111 if node.Body != nil { 112 v.insideFunc = true 113 ast.Walk(v, node.Body) 114 v.insideFunc = false 115 } 116 117 if node.Recv != nil { 118 ast.Walk(v, node.Recv) 119 } 120 if node.Type != nil { 121 ast.Walk(v, node.Type) 122 } 123 124 return nil 125 } 126 127 return v 128 } 129 130 func main() { 131 flag.Parse() 132 exitStatus := 0 133 importPaths := gotool.ImportPaths(flag.Args()) 134 if len(importPaths) == 0 { 135 importPaths = []string{"."} 136 } 137 138 ctx := build.Default 139 loadcfg := loader.Config{ 140 Build: &ctx, 141 } 142 rest, err := loadcfg.FromArgs(importPaths, true) 143 if err != nil { 144 log.Fatalf("could not parse arguments: %s", err) 145 } 146 if len(rest) > 0 { 147 log.Fatalf("unhandled extra arguments: %v", rest) 148 } 149 150 program, err := loadcfg.Load() 151 if err != nil { 152 log.Fatalf("could not type check: %s", err) 153 } 154 155 uses := make(map[object]int) 156 positions := make(map[object]token.Position) 157 158 for _, pkgInfo := range program.InitialPackages() { 159 if pkgInfo.Pkg.Path() == "unsafe" { 160 continue 161 } 162 163 v := &visitor{ 164 prog: program, 165 pkg: pkgInfo, 166 uses: uses, 167 positions: positions, 168 } 169 170 for _, f := range v.pkg.Files { 171 ast.Walk(v, f) 172 } 173 } 174 175 var lines []string 176 177 for obj, useCount := range uses { 178 if useCount == 0 && (*reportExported || !ast.IsExported(obj.name)) { 179 pos := positions[obj] 180 lines = append(lines, fmt.Sprintf("%s: %s:%d:%d: %s", obj.pkgPath, pos.Filename, pos.Line, pos.Column, obj.name)) 181 exitStatus = 1 182 } 183 } 184 185 sort.Strings(lines) 186 for _, line := range lines { 187 fmt.Println(line) 188 } 189 190 os.Exit(exitStatus) 191 }