├── LICENSE └── deadcode.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Rémy Oudompheng. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * The name of Rémy Oudompheng may not be used to endorse or promote products derived from 14 | this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | -------------------------------------------------------------------------------- /deadcode.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "go/ast" 7 | "go/parser" 8 | "go/token" 9 | "os" 10 | "sort" 11 | "strings" 12 | ) 13 | 14 | var exitCode int 15 | 16 | func main() { 17 | flag.Parse() 18 | if flag.NArg() == 0 { 19 | doDir(".") 20 | } else { 21 | for _, name := range flag.Args() { 22 | // Is it a directory? 23 | if fi, err := os.Stat(name); err == nil && fi.IsDir() { 24 | doDir(name) 25 | } else { 26 | errorf("not a directory: %s", name) 27 | } 28 | } 29 | } 30 | os.Exit(exitCode) 31 | } 32 | 33 | // error formats the error to standard error, adding program 34 | // identification and a newline 35 | func errorf(format string, args ...interface{}) { 36 | fmt.Fprintf(os.Stderr, "deadcode: "+format+"\n", args...) 37 | exitCode = 2 38 | } 39 | 40 | func doDir(name string) { 41 | notests := func(info os.FileInfo) bool { 42 | if !info.IsDir() && strings.HasSuffix(info.Name(), ".go") && 43 | !strings.HasSuffix(info.Name(), "_test.go") { 44 | return true 45 | } 46 | return false 47 | } 48 | fs := token.NewFileSet() 49 | pkgs, err := parser.ParseDir(fs, name, notests, parser.Mode(0)) 50 | if err != nil { 51 | errorf("%s", err) 52 | return 53 | } 54 | for _, pkg := range pkgs { 55 | doPackage(fs, pkg) 56 | } 57 | } 58 | 59 | type Package struct { 60 | p *ast.Package 61 | fs *token.FileSet 62 | decl map[string]ast.Node 63 | used map[string]bool 64 | } 65 | 66 | func doPackage(fs *token.FileSet, pkg *ast.Package) { 67 | p := &Package{ 68 | p: pkg, 69 | fs: fs, 70 | decl: make(map[string]ast.Node), 71 | used: make(map[string]bool), 72 | } 73 | for _, file := range pkg.Files { 74 | for _, decl := range file.Decls { 75 | switch n := decl.(type) { 76 | case *ast.GenDecl: 77 | // var, const, types 78 | for _, spec := range n.Specs { 79 | switch s := spec.(type) { 80 | case *ast.ValueSpec: 81 | // constants and variables. 82 | for _, name := range s.Names { 83 | p.decl[name.Name] = n 84 | } 85 | case *ast.TypeSpec: 86 | // type definitions. 87 | p.decl[s.Name.Name] = n 88 | } 89 | } 90 | case *ast.FuncDecl: 91 | // function declarations 92 | // TODO(remy): do methods 93 | if n.Recv == nil { 94 | p.decl[n.Name.Name] = n 95 | } 96 | } 97 | } 98 | } 99 | // init() and _ are always used 100 | p.used["init"] = true 101 | p.used["_"] = true 102 | if pkg.Name != "main" { 103 | // exported names are marked used for non-main packages. 104 | for name := range p.decl { 105 | if ast.IsExported(name) { 106 | p.used[name] = true 107 | } 108 | } 109 | } else { 110 | // in main programs, main() is called. 111 | p.used["main"] = true 112 | } 113 | for _, file := range pkg.Files { 114 | // walk file looking for used nodes. 115 | ast.Walk(p, file) 116 | } 117 | // reports. 118 | reports := Reports(nil) 119 | for name, node := range p.decl { 120 | if !p.used[name] { 121 | reports = append(reports, Report{node.Pos(), name}) 122 | } 123 | } 124 | sort.Sort(reports) 125 | for _, report := range reports { 126 | errorf("%s: %s is unused", fs.Position(report.pos), report.name) 127 | } 128 | } 129 | 130 | type Report struct { 131 | pos token.Pos 132 | name string 133 | } 134 | type Reports []Report 135 | 136 | func (l Reports) Len() int { return len(l) } 137 | func (l Reports) Less(i, j int) bool { return l[i].pos < l[j].pos } 138 | func (l Reports) Swap(i, j int) { l[i], l[j] = l[j], l[i] } 139 | 140 | // Visits files for used nodes. 141 | func (p *Package) Visit(node ast.Node) ast.Visitor { 142 | u := usedWalker(*p) // hopefully p fields are references. 143 | switch n := node.(type) { 144 | // don't walk whole file, but only: 145 | case *ast.ValueSpec: 146 | // - variable initializers 147 | for _, value := range n.Values { 148 | ast.Walk(&u, value) 149 | } 150 | // variable types. 151 | if n.Type != nil { 152 | ast.Walk(&u, n.Type) 153 | } 154 | case *ast.BlockStmt: 155 | // - function bodies 156 | for _, stmt := range n.List { 157 | ast.Walk(&u, stmt) 158 | } 159 | case *ast.FuncDecl: 160 | // - function signatures 161 | ast.Walk(&u, n.Type) 162 | case *ast.TypeSpec: 163 | // - type declarations 164 | ast.Walk(&u, n.Type) 165 | } 166 | return p 167 | } 168 | 169 | type usedWalker Package 170 | 171 | // Walks through the AST marking used identifiers. 172 | func (p *usedWalker) Visit(node ast.Node) ast.Visitor { 173 | // just be stupid and mark all *ast.Ident 174 | switch n := node.(type) { 175 | case *ast.Ident: 176 | p.used[n.Name] = true 177 | } 178 | return p 179 | } 180 | --------------------------------------------------------------------------------