2018-05-06 14:52:59 +03:00

126 lines
2.6 KiB
Go

package deadcode
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"os"
"path/filepath"
"sort"
"golang.org/x/tools/go/loader"
)
var exitCode int
var (
withTestFiles bool
)
type Issue struct {
Pos token.Position
UnusedIdentName string
}
func Run(paths []string, processTestFiles bool) ([]Issue, error) {
ctx := &Context{
withTests: processTestFiles,
}
ctx.Load(paths...)
report := ctx.Process()
var issues []Issue
for _, obj := range report {
issues = append(issues, Issue{
Pos: ctx.Config.Fset.Position(obj.Pos()),
UnusedIdentName: obj.Name(),
})
}
return issues, nil
}
func fatalf(format string, args ...interface{}) {
panic(fmt.Errorf(format, args...))
}
type Context struct {
cwd string
withTests bool
loader.Config
}
func (ctx *Context) Load(args ...string) {
ctx.Config.FromArgs(args, ctx.withTests)
}
// error formats the error to standard error, adding program
// identification and a newline
func (ctx *Context) errorf(pos token.Pos, format string, args ...interface{}) {
if ctx.cwd == "" {
ctx.cwd, _ = os.Getwd()
}
p := ctx.Config.Fset.Position(pos)
f, err := filepath.Rel(ctx.cwd, p.Filename)
if err == nil {
p.Filename = f
}
fmt.Fprintf(os.Stderr, p.String()+": "+format+"\n", args...)
exitCode = 2
}
func (ctx *Context) Process() []types.Object {
prog, err := ctx.Config.Load()
if err != nil {
fatalf("cannot load packages: %s", err)
}
var allUnused []types.Object
for _, pkg := range prog.Imported {
unused := doPackage(prog, pkg)
allUnused = append(allUnused, unused...)
}
for _, pkg := range prog.Created {
unused := doPackage(prog, pkg)
allUnused = append(allUnused, unused...)
}
sort.Sort(objects(allUnused))
return allUnused
}
func doPackage(prog *loader.Program, pkg *loader.PackageInfo) []types.Object {
used := make(map[types.Object]bool)
for _, file := range pkg.Files {
ast.Inspect(file, func(n ast.Node) bool {
id, ok := n.(*ast.Ident)
if !ok {
return true
}
obj := pkg.Info.Uses[id]
if obj != nil {
used[obj] = true
}
return false
})
}
global := pkg.Pkg.Scope()
var unused []types.Object
for _, name := range global.Names() {
if pkg.Pkg.Name() == "main" && name == "main" {
continue
}
obj := global.Lookup(name)
if !used[obj] && (pkg.Pkg.Name() == "main" || !ast.IsExported(name)) {
unused = append(unused, obj)
}
}
return unused
}
type objects []types.Object
func (s objects) Len() int { return len(s) }
func (s objects) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s objects) Less(i, j int) bool { return s[i].Pos() < s[j].Pos() }