mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-03 23:26:29 +00:00
c28f7cb29f
Initial part of #435
135 lines
3 KiB
Go
135 lines
3 KiB
Go
package deadcode
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/go/loader"
|
|
)
|
|
|
|
var exitCode int
|
|
|
|
var (
|
|
withTestFiles bool
|
|
)
|
|
|
|
type Issue struct {
|
|
Pos token.Position
|
|
UnusedIdentName string
|
|
}
|
|
|
|
func Run(program *loader.Program) ([]Issue, error) {
|
|
ctx := &Context{
|
|
program: program,
|
|
}
|
|
report := ctx.Process()
|
|
var issues []Issue
|
|
for _, obj := range report {
|
|
issues = append(issues, Issue{
|
|
Pos: program.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
|
|
|
|
program *loader.Program
|
|
}
|
|
|
|
// pos resolves a compact position encoding into a verbose one
|
|
func (ctx *Context) pos(pos token.Pos) token.Position {
|
|
if ctx.cwd == "" {
|
|
ctx.cwd, _ = os.Getwd()
|
|
}
|
|
p := ctx.program.Fset.Position(pos)
|
|
f, err := filepath.Rel(ctx.cwd, p.Filename)
|
|
if err == nil {
|
|
p.Filename = f
|
|
}
|
|
return p
|
|
}
|
|
|
|
// error formats the error to standard error, adding program
|
|
// identification and a newline
|
|
func (ctx *Context) errorf(pos token.Pos, format string, args ...interface{}) {
|
|
p := ctx.pos(pos)
|
|
fmt.Fprintf(os.Stderr, p.String()+": "+format+"\n", args...)
|
|
exitCode = 2
|
|
}
|
|
|
|
func (ctx *Context) Load(args ...string) {
|
|
// TODO
|
|
}
|
|
|
|
func (ctx *Context) Process() []types.Object {
|
|
prog := ctx.program
|
|
var allUnused []types.Object
|
|
for _, pkg := range prog.Imported {
|
|
unused := ctx.doPackage(prog, pkg)
|
|
allUnused = append(allUnused, unused...)
|
|
}
|
|
for _, pkg := range prog.Created {
|
|
unused := ctx.doPackage(prog, pkg)
|
|
allUnused = append(allUnused, unused...)
|
|
}
|
|
sort.Sort(objects(allUnused))
|
|
return allUnused
|
|
}
|
|
|
|
func isTestFuncByName(name string) bool {
|
|
return strings.HasPrefix(name, "Test") || strings.HasPrefix(name, "Benchmark") || strings.HasPrefix(name, "Example")
|
|
}
|
|
|
|
func (ctx *Context) 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)
|
|
_, isSig := obj.Type().(*types.Signature)
|
|
pos := ctx.pos(obj.Pos())
|
|
isTestMethod := isSig && isTestFuncByName(obj.Name()) && strings.HasSuffix(pos.Filename, "_test.go")
|
|
if !used[obj] && ((pkg.Pkg.Name() == "main" && !isTestMethod) || !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() }
|