mirror of
https://github.com/mgechev/revive.git
synced 2025-11-23 22:04:49 +02:00
* refactor: fix thelper issues test/utils_test.go:19:6 thelper test helper function should start from t.Helper() test/utils_test.go:42:6 thelper test helper function should start from t.Helper() test/utils_test.go:63:6 thelper test helper function should start from t.Helper() test/utils_test.go:146:6 thelper test helper function should start from t.Helper() * refactor: fix govet issues rule/error_strings.go:50:21 govet printf: non-constant format string in call to fmt.Errorf * refactor: fix gosimple issue rule/bare_return.go:52:9 gosimple S1016: should convert w (type lintBareReturnRule) to bareReturnFinder instead of using struct literal * refactor: fix errorlint issues lint/filefilter.go:70:86 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/filefilter.go:113:104 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/filefilter.go:125:89 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/linter.go:166:72 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors lint/linter.go:171:73 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors config/config.go:174:57 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors config/config.go:179:64 errorlint non-wrapping format verb for fmt.Errorf. Use `%w` to format errors * refactor: fix revive issue about comment spacing cli/main.go:31:2 revive comment-spacings: no space between comment delimiter and comment text * refactor: fix revive issue about unused-receiver revivelib/core_test.go:77:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ revivelib/core_test.go:81:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/context_as_argument.go:76:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/var_naming.go:73:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/modifies_value_receiver.go:59:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ rule/filename_format.go:43:7 revive unused-receiver: method receiver 'r' is not referenced in method's body, consider removing or renaming it as _ * refactor: fix revive issues about unused-parameter revivelib/core_test.go:81:24 revive unused-parameter: parameter 'file' seems to be unused, consider removing or renaming it as _ revivelib/core_test.go:81:41 revive unused-parameter: parameter 'arguments' seems to be unused, consider removing or renaming it as _ * refactor: fix gocritic issues about commentedOutCode test/utils_test.go:119:5 gocritic commentedOutCode: may want to remove commented-out code rule/unreachable_code.go:65:3 gocritic commentedOutCode: may want to remove commented-out code
184 lines
4.1 KiB
Go
184 lines
4.1 KiB
Go
package rule
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/token"
|
|
"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
|
|
|
|
file.Pkg.TypeCheck()
|
|
for _, decl := range file.AST.Decls {
|
|
funcDecl, ok := decl.(*ast.FuncDecl)
|
|
isAMethod := ok && funcDecl.Recv != nil
|
|
if !isAMethod {
|
|
continue // skip, not a method
|
|
}
|
|
|
|
receiver := funcDecl.Recv.List[0]
|
|
if r.mustSkip(receiver, file.Pkg) {
|
|
continue
|
|
}
|
|
|
|
receiverName := receiver.Names[0].Name
|
|
assignmentsToReceiver := r.getReceiverModifications(receiverName, funcDecl.Body)
|
|
if len(assignmentsToReceiver) == 0 {
|
|
continue // receiver is not modified
|
|
}
|
|
|
|
methodReturnsReceiver := len(r.findReturnReceiverStatements(receiverName, funcDecl.Body)) > 0
|
|
if methodReturnsReceiver {
|
|
continue // modification seems legit (see issue #1066)
|
|
}
|
|
|
|
for _, assignment := range assignmentsToReceiver {
|
|
failures = append(failures, lint.Failure{
|
|
Node: assignment,
|
|
Confidence: 1,
|
|
Failure: "suspicious assignment to a by-value method receiver",
|
|
})
|
|
}
|
|
}
|
|
|
|
return failures
|
|
}
|
|
|
|
// Name returns the rule name.
|
|
func (*ModifiesValRecRule) Name() string {
|
|
return "modifies-value-receiver"
|
|
}
|
|
|
|
func (*ModifiesValRecRule) skipType(t ast.Expr, pkg *lint.Package) bool {
|
|
rt := 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 (*ModifiesValRecRule) getNameFromExpr(ie ast.Expr) string {
|
|
ident, ok := ie.(*ast.Ident)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
|
|
return ident.Name
|
|
}
|
|
|
|
func (r *ModifiesValRecRule) findReturnReceiverStatements(receiverName string, target ast.Node) []ast.Node {
|
|
finder := func(n ast.Node) bool {
|
|
// look for returns with the receiver as value
|
|
returnStatement, ok := n.(*ast.ReturnStmt)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
for _, exp := range returnStatement.Results {
|
|
switch e := exp.(type) {
|
|
case *ast.SelectorExpr: // receiver.field = ...
|
|
name := r.getNameFromExpr(e.X)
|
|
if name == "" || name != receiverName {
|
|
continue
|
|
}
|
|
case *ast.Ident: // receiver := ...
|
|
if e.Name != receiverName {
|
|
continue
|
|
}
|
|
case *ast.UnaryExpr:
|
|
if e.Op != token.AND {
|
|
continue
|
|
}
|
|
name := r.getNameFromExpr(e.X)
|
|
if name == "" || name != receiverName {
|
|
continue
|
|
}
|
|
|
|
default:
|
|
continue
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
return pick(target, finder)
|
|
}
|
|
|
|
func (r *ModifiesValRecRule) mustSkip(receiver *ast.Field, pkg *lint.Package) bool {
|
|
if _, ok := receiver.Type.(*ast.StarExpr); ok {
|
|
return true // skip, method with pointer receiver
|
|
}
|
|
|
|
if len(receiver.Names) < 1 {
|
|
return true // skip, anonymous receiver
|
|
}
|
|
|
|
receiverName := receiver.Names[0].Name
|
|
if receiverName == "_" {
|
|
return true // skip, anonymous receiver
|
|
}
|
|
|
|
if r.skipType(receiver.Type, pkg) {
|
|
return true // skip, receiver is a map or array
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (r *ModifiesValRecRule) getReceiverModifications(receiverName string, funcBody *ast.BlockStmt) []ast.Node {
|
|
receiverModificationFinder := func(n ast.Node) bool {
|
|
switch node := n.(type) {
|
|
case *ast.IncDecStmt:
|
|
se, ok := node.X.(*ast.SelectorExpr)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
name := r.getNameFromExpr(se.X)
|
|
return name == receiverName
|
|
case *ast.AssignStmt:
|
|
// look for assignments with the receiver in the right hand
|
|
for _, exp := range node.Lhs {
|
|
switch e := exp.(type) {
|
|
case *ast.IndexExpr: // receiver...[] = ...
|
|
continue
|
|
case *ast.StarExpr: // *receiver = ...
|
|
continue
|
|
case *ast.SelectorExpr: // receiver.field = ...
|
|
name := r.getNameFromExpr(e.X)
|
|
if name == "" || name != receiverName {
|
|
continue
|
|
}
|
|
case *ast.Ident: // receiver := ...
|
|
if e.Name != receiverName {
|
|
continue
|
|
}
|
|
default:
|
|
continue
|
|
}
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
return pick(funcBody, receiverModificationFinder)
|
|
}
|