1
0
mirror of https://github.com/mgechev/revive.git synced 2025-11-23 22:04:49 +02:00
Files
revive/rule/unused_receiver.go

106 lines
3.0 KiB
Go
Raw Normal View History

package rule
import (
"fmt"
"go/ast"
"regexp"
"github.com/mgechev/revive/internal/astutils"
"github.com/mgechev/revive/lint"
)
2024-12-01 17:44:41 +02:00
// UnusedReceiverRule lints unused receivers in functions.
type UnusedReceiverRule struct {
// regex to check if some name is valid for unused parameter, "^_$" by default
allowRegex *regexp.Regexp
failureMsg string
}
// Configure validates the rule configuration, and configures the rule accordingly.
//
// Configuration implements the [lint.ConfigurableRule] interface.
func (r *UnusedReceiverRule) Configure(args lint.Arguments) error {
2024-12-31 12:33:51 +01:00
// while by default args is an array, it could be good to provide structures inside it by default, not arrays or primitives
// as it's more compatible to JSON nature of configurations
r.allowRegex = allowBlankIdentifierRegex
r.failureMsg = "method receiver '%s' is not referenced in method's body, consider removing or renaming it as _"
if len(args) == 0 {
return nil
}
// Arguments = [{}]
options, ok := args[0].(map[string]any)
if !ok {
return nil
}
for k, v := range options {
if !isRuleOption(k, "allowRegex") {
return nil
}
// Arguments = [{allowRegex="^_"}]
allowRegexStr, ok := v.(string)
if !ok {
return fmt.Errorf("error configuring [unused-receiver] rule: allowRegex is not string but [%T]", v)
}
var err error
r.allowRegex, err = regexp.Compile(allowRegexStr)
if err != nil {
return fmt.Errorf("error configuring [unused-receiver] rule: allowRegex is not valid regex [%s]: %w", allowRegexStr, err)
}
r.failureMsg = "method receiver '%s' is not referenced in method's body, consider removing or renaming it to match " + r.allowRegex.String()
}
return nil
}
// Apply applies the rule to given file.
func (r *UnusedReceiverRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
for _, decl := range file.AST.Decls {
funcDecl, ok := decl.(*ast.FuncDecl)
isMethod := ok && funcDecl.Recv != nil
if !isMethod {
continue
}
rec := funcDecl.Recv.List[0] // safe to access only the first (unique) element of the list
if len(rec.Names) < 1 {
continue // the receiver is anonymous: func (aType) Foo(...) ...
}
recID := rec.Names[0]
if recID.Name == "_" {
continue // the receiver is already named _
}
if r.allowRegex != nil && r.allowRegex.FindStringIndex(recID.Name) != nil {
continue
}
// inspect the func body looking for references to the receiver id
selectReceiverUses := func(n ast.Node) bool {
ident, isAnID := n.(*ast.Ident)
return isAnID && ident.Obj == recID.Obj
}
receiverUse := astutils.SeekNode[ast.Node](funcDecl.Body, selectReceiverUses)
if receiverUse != nil {
continue // the receiver is referenced in the func body
}
failures = append(failures, lint.Failure{
Confidence: 1,
Node: recID,
Category: lint.FailureCategoryBadPractice,
Failure: fmt.Sprintf(r.failureMsg, recID.Name),
})
}
return failures
}
// Name returns the rule name.
func (*UnusedReceiverRule) Name() string {
return "unused-receiver"
}