1
0
mirror of https://github.com/mgechev/revive.git synced 2025-01-10 03:17:11 +02:00
revive/lint/package.go
dominiquelefevre 4242f24f4d
Add support for the new implementation of for loop variables in go 1.22. (#993)
* 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>
2024-06-02 11:55:26 +02:00

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