123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- 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()
- }
|