forked from Shiloh/githaven
179 lines
4.0 KiB
Go
179 lines
4.0 KiB
Go
|
package lint
|
||
|
|
||
|
import (
|
||
|
"go/ast"
|
||
|
"go/token"
|
||
|
"go/types"
|
||
|
"sync"
|
||
|
|
||
|
"golang.org/x/tools/go/gcexportdata"
|
||
|
)
|
||
|
|
||
|
// Package represents a package in the project.
|
||
|
type Package struct {
|
||
|
fset *token.FileSet
|
||
|
files map[string]*File
|
||
|
|
||
|
TypesPkg *types.Package
|
||
|
TypesInfo *types.Info
|
||
|
|
||
|
// sortable is the set of types in the package that implement sort.Interface.
|
||
|
Sortable map[string]bool
|
||
|
// main is whether this is a "main" package.
|
||
|
main int
|
||
|
mu sync.Mutex
|
||
|
}
|
||
|
|
||
|
var newImporter = func(fset *token.FileSet) types.ImporterFrom {
|
||
|
return gcexportdata.NewImporter(fset, make(map[string]*types.Package))
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
trueValue = 1
|
||
|
falseValue = 2
|
||
|
notSet = 3
|
||
|
)
|
||
|
|
||
|
// IsMain returns if that's the main package.
|
||
|
func (p *Package) IsMain() bool {
|
||
|
if p.main == trueValue {
|
||
|
return true
|
||
|
} else if p.main == falseValue {
|
||
|
return false
|
||
|
}
|
||
|
for _, f := range p.files {
|
||
|
if f.isMain() {
|
||
|
p.main = trueValue
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
p.main = falseValue
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// TypeCheck performs type checking for given package.
|
||
|
func (p *Package) TypeCheck() error {
|
||
|
p.mu.Lock()
|
||
|
// If type checking has already been performed
|
||
|
// skip it.
|
||
|
if p.TypesInfo != nil || p.TypesPkg != nil {
|
||
|
p.mu.Unlock()
|
||
|
return nil
|
||
|
}
|
||
|
config := &types.Config{
|
||
|
// By setting a no-op error reporter, the type checker does as much work as possible.
|
||
|
Error: func(error) {},
|
||
|
Importer: newImporter(p.fset),
|
||
|
}
|
||
|
info := &types.Info{
|
||
|
Types: make(map[ast.Expr]types.TypeAndValue),
|
||
|
Defs: make(map[*ast.Ident]types.Object),
|
||
|
Uses: make(map[*ast.Ident]types.Object),
|
||
|
Scopes: make(map[ast.Node]*types.Scope),
|
||
|
}
|
||
|
var anyFile *File
|
||
|
var astFiles []*ast.File
|
||
|
for _, f := range p.files {
|
||
|
anyFile = f
|
||
|
astFiles = append(astFiles, f.AST)
|
||
|
}
|
||
|
|
||
|
typesPkg, err := check(config, anyFile.AST.Name.Name, p.fset, astFiles, info)
|
||
|
|
||
|
// Remember the typechecking info, even if config.Check failed,
|
||
|
// since we will get partial information.
|
||
|
p.TypesPkg = typesPkg
|
||
|
p.TypesInfo = info
|
||
|
p.mu.Unlock()
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// check function encapsulates the call to go/types.Config.Check method and
|
||
|
// recovers if the called method panics (see issue #59)
|
||
|
func check(config *types.Config, n string, fset *token.FileSet, astFiles []*ast.File, info *types.Info) (p *types.Package, err error) {
|
||
|
defer func() {
|
||
|
if r := recover(); r != nil {
|
||
|
err, _ = r.(error)
|
||
|
p = nil
|
||
|
return
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
return config.Check(n, fset, astFiles, info)
|
||
|
}
|
||
|
|
||
|
// TypeOf returns the type of an expression.
|
||
|
func (p *Package) TypeOf(expr ast.Expr) types.Type {
|
||
|
if p.TypesInfo == nil {
|
||
|
return nil
|
||
|
}
|
||
|
return p.TypesInfo.TypeOf(expr)
|
||
|
}
|
||
|
|
||
|
type walker struct {
|
||
|
nmap map[string]int
|
||
|
has map[string]int
|
||
|
}
|
||
|
|
||
|
func (w *walker) Visit(n ast.Node) ast.Visitor {
|
||
|
fn, ok := n.(*ast.FuncDecl)
|
||
|
if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 {
|
||
|
return w
|
||
|
}
|
||
|
// TODO(dsymonds): We could check the signature to be more precise.
|
||
|
recv := receiverType(fn)
|
||
|
if i, ok := w.nmap[fn.Name.Name]; ok {
|
||
|
w.has[recv] |= i
|
||
|
}
|
||
|
return w
|
||
|
}
|
||
|
|
||
|
func (p *Package) scanSortable() {
|
||
|
p.Sortable = make(map[string]bool)
|
||
|
|
||
|
// bitfield for which methods exist on each type.
|
||
|
const (
|
||
|
Len = 1 << iota
|
||
|
Less
|
||
|
Swap
|
||
|
)
|
||
|
nmap := map[string]int{"Len": Len, "Less": Less, "Swap": Swap}
|
||
|
has := make(map[string]int)
|
||
|
for _, f := range p.files {
|
||
|
ast.Walk(&walker{nmap, has}, f.AST)
|
||
|
}
|
||
|
for typ, ms := range has {
|
||
|
if ms == Len|Less|Swap {
|
||
|
p.Sortable[typ] = true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// receiverType returns the named type of the method receiver, sans "*",
|
||
|
// or "invalid-type" if fn.Recv is ill formed.
|
||
|
func receiverType(fn *ast.FuncDecl) string {
|
||
|
switch e := fn.Recv.List[0].Type.(type) {
|
||
|
case *ast.Ident:
|
||
|
return e.Name
|
||
|
case *ast.StarExpr:
|
||
|
if id, ok := e.X.(*ast.Ident); ok {
|
||
|
return id.Name
|
||
|
}
|
||
|
}
|
||
|
// The parser accepts much more than just the legal forms.
|
||
|
return "invalid-type"
|
||
|
}
|
||
|
|
||
|
func (p *Package) lint(rules []Rule, config Config, failures chan Failure) {
|
||
|
p.scanSortable()
|
||
|
var wg sync.WaitGroup
|
||
|
for _, file := range p.files {
|
||
|
wg.Add(1)
|
||
|
go (func(file *File) {
|
||
|
file.lint(rules, config, failures)
|
||
|
defer wg.Done()
|
||
|
})(file)
|
||
|
}
|
||
|
wg.Wait()
|
||
|
}
|