mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	
		
			
				
	
	
		
			135 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			135 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package rule
 | 
						|
 | 
						|
import (
 | 
						|
	"go/ast"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/mgechev/revive/lint"
 | 
						|
)
 | 
						|
 | 
						|
// ModifiesValRecRule lints assignments to value method-receivers.
 | 
						|
type ModifiesValRecRule struct{}
 | 
						|
 | 
						|
// Apply applies the rule to given file.
 | 
						|
func (r *ModifiesValRecRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
 | 
						|
	var failures []lint.Failure
 | 
						|
 | 
						|
	onFailure := func(failure lint.Failure) {
 | 
						|
		failures = append(failures, failure)
 | 
						|
	}
 | 
						|
 | 
						|
	w := lintModifiesValRecRule{file: file, onFailure: onFailure}
 | 
						|
	file.Pkg.TypeCheck()
 | 
						|
	ast.Walk(w, file.AST)
 | 
						|
 | 
						|
	return failures
 | 
						|
}
 | 
						|
 | 
						|
// Name returns the rule name.
 | 
						|
func (r *ModifiesValRecRule) Name() string {
 | 
						|
	return "modifies-value-receiver"
 | 
						|
}
 | 
						|
 | 
						|
type lintModifiesValRecRule struct {
 | 
						|
	file      *lint.File
 | 
						|
	onFailure func(lint.Failure)
 | 
						|
}
 | 
						|
 | 
						|
func (w lintModifiesValRecRule) Visit(node ast.Node) ast.Visitor {
 | 
						|
	switch n := node.(type) {
 | 
						|
	case *ast.FuncDecl:
 | 
						|
		if n.Recv == nil {
 | 
						|
			return nil // skip, not a method
 | 
						|
		}
 | 
						|
 | 
						|
		receiver := n.Recv.List[0]
 | 
						|
		if _, ok := receiver.Type.(*ast.StarExpr); ok {
 | 
						|
			return nil // skip, method with pointer receiver
 | 
						|
		}
 | 
						|
 | 
						|
		if w.skipType(receiver.Type) {
 | 
						|
			return nil // skip, receiver is a map or array
 | 
						|
		}
 | 
						|
 | 
						|
		if len(receiver.Names) < 1 {
 | 
						|
			return nil // skip, anonymous receiver
 | 
						|
		}
 | 
						|
 | 
						|
		receiverName := receiver.Names[0].Name
 | 
						|
		if receiverName == "_" {
 | 
						|
			return nil // skip, anonymous receiver
 | 
						|
		}
 | 
						|
 | 
						|
		fselect := func(n ast.Node) bool {
 | 
						|
			// look for assignments with the receiver in the right hand
 | 
						|
			asgmt, ok := n.(*ast.AssignStmt)
 | 
						|
			if !ok {
 | 
						|
				return false
 | 
						|
			}
 | 
						|
 | 
						|
			for _, exp := range asgmt.Lhs {
 | 
						|
				switch e := exp.(type) {
 | 
						|
				case *ast.IndexExpr: // receiver...[] = ...
 | 
						|
					continue
 | 
						|
				case *ast.StarExpr: // *receiver = ...
 | 
						|
					continue
 | 
						|
				case *ast.SelectorExpr: // receiver.field = ...
 | 
						|
					name := w.getNameFromExpr(e.X)
 | 
						|
					if name == "" || name != receiverName {
 | 
						|
						continue
 | 
						|
					}
 | 
						|
 | 
						|
					if w.skipType(ast.Expr(e.Sel)) {
 | 
						|
						continue
 | 
						|
					}
 | 
						|
 | 
						|
				case *ast.Ident: // receiver := ...
 | 
						|
					if e.Name != receiverName {
 | 
						|
						continue
 | 
						|
					}
 | 
						|
				default:
 | 
						|
					continue
 | 
						|
				}
 | 
						|
 | 
						|
				return true
 | 
						|
			}
 | 
						|
 | 
						|
			return false
 | 
						|
		}
 | 
						|
 | 
						|
		assignmentsToReceiver := pick(n.Body, fselect, nil)
 | 
						|
 | 
						|
		for _, assignment := range assignmentsToReceiver {
 | 
						|
			w.onFailure(lint.Failure{
 | 
						|
				Node:       assignment,
 | 
						|
				Confidence: 1,
 | 
						|
				Failure:    "suspicious assignment to a by-value method receiver",
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return w
 | 
						|
}
 | 
						|
 | 
						|
func (w lintModifiesValRecRule) skipType(t ast.Expr) bool {
 | 
						|
	rt := w.file.Pkg.TypeOf(t)
 | 
						|
	if rt == nil {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	rt = rt.Underlying()
 | 
						|
	rtName := rt.String()
 | 
						|
 | 
						|
	// skip when receiver is a map or array
 | 
						|
	return strings.HasPrefix(rtName, "[]") || strings.HasPrefix(rtName, "map[")
 | 
						|
}
 | 
						|
 | 
						|
func (lintModifiesValRecRule) getNameFromExpr(ie ast.Expr) string {
 | 
						|
	ident, ok := ie.(*ast.Ident)
 | 
						|
	if !ok {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
 | 
						|
	return ident.Name
 | 
						|
}
 |