mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 11:28:24 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			179 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
			
		
		
	
	
			179 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
| 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()
 | |
| }
 |