mirror of
https://github.com/mgechev/revive.git
synced 2025-01-10 03:17:11 +02:00
4242f24f4d
* Add support for the new behaviour of for loops in go 1.22. Go 1.22 has changed the behaviour of for loops. Every iteration makes new loop variables. It is now safe to take their addresses because they are guaranteed to be unique. Similarly, it is now safe to capture loop variables in functions. * adds documentation for public function --------- Co-authored-by: chavacava <salvadorcavadini+github@gmail.com>
201 lines
4.3 KiB
Go
201 lines
4.3 KiB
Go
package lint
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/importer"
|
|
"go/token"
|
|
"go/types"
|
|
"sync"
|
|
|
|
goversion "github.com/hashicorp/go-version"
|
|
|
|
"github.com/mgechev/revive/internal/typeparams"
|
|
)
|
|
|
|
// Package represents a package in the project.
|
|
type Package struct {
|
|
fset *token.FileSet
|
|
files map[string]*File
|
|
goVersion *goversion.Version
|
|
|
|
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
|
|
sync.RWMutex
|
|
}
|
|
|
|
var (
|
|
trueValue = 1
|
|
falseValue = 2
|
|
notSet = 3
|
|
|
|
go122 = goversion.Must(goversion.NewVersion("1.22"))
|
|
)
|
|
|
|
// Files return package's files.
|
|
func (p *Package) Files() map[string]*File {
|
|
return p.files
|
|
}
|
|
|
|
// IsMain returns if that's the main package.
|
|
func (p *Package) IsMain() bool {
|
|
p.Lock()
|
|
defer p.Unlock()
|
|
|
|
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
|
|
}
|
|
|
|
// TypesPkg yields information on this package
|
|
func (p *Package) TypesPkg() *types.Package {
|
|
p.RLock()
|
|
defer p.RUnlock()
|
|
return p.typesPkg
|
|
}
|
|
|
|
// TypesInfo yields type information of this package identifiers
|
|
func (p *Package) TypesInfo() *types.Info {
|
|
p.RLock()
|
|
defer p.RUnlock()
|
|
return p.typesInfo
|
|
}
|
|
|
|
// Sortable yields a map of sortable types in this package
|
|
func (p *Package) Sortable() map[string]bool {
|
|
p.RLock()
|
|
defer p.RUnlock()
|
|
return p.sortable
|
|
}
|
|
|
|
// TypeCheck performs type checking for given package.
|
|
func (p *Package) TypeCheck() error {
|
|
p.Lock()
|
|
defer p.Unlock()
|
|
|
|
// If type checking has already been performed
|
|
// skip it.
|
|
if p.typesInfo != nil || p.typesPkg != nil {
|
|
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: importer.Default(),
|
|
}
|
|
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
|
|
|
|
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 := typeparams.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
|
|
}
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
// IsAtLeastGo122 returns true if the Go version for this package is 1.22 or higher, false otherwise
|
|
func (p *Package) IsAtLeastGo122() bool {
|
|
return p.goVersion.GreaterThanOrEqual(go122)
|
|
}
|