Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
4.0 KiB
178 lines
4.0 KiB
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() |
|
}
|
|
|