1
0
mirror of https://github.com/mgechev/revive.git synced 2024-11-30 08:57:07 +02:00
revive/rule/unused-param.go
2024-01-27 13:39:55 +01:00

168 lines
3.6 KiB
Go

package rule
import (
"fmt"
"go/ast"
"regexp"
"sync"
"github.com/mgechev/revive/lint"
)
// UnusedParamRule lints unused params in functions.
type UnusedParamRule struct {
configured bool
// regex to check if some name is valid for unused parameter, "^_$" by default
allowRegex *regexp.Regexp
failureMsg string
sync.Mutex
}
func (r *UnusedParamRule) configure(args lint.Arguments) {
r.Lock()
defer r.Unlock()
if r.configured {
return
}
r.configured = true
// while by default args is an array, i think it's good to provide structures inside it by default, not arrays or primitives
// it's more compatible to JSON nature of configurations
var allowedRegexStr string
if len(args) == 0 {
allowedRegexStr = "^_$"
r.failureMsg = "parameter '%s' seems to be unused, consider removing or renaming it as _"
} else {
// Arguments = [{}]
options := args[0].(map[string]any)
// Arguments = [{allowedRegex="^_"}]
if allowedRegexParam, ok := options["allowRegex"]; ok {
allowedRegexStr, ok = allowedRegexParam.(string)
if !ok {
panic(fmt.Errorf("error configuring %s rule: allowedRegex is not string but [%T]", r.Name(), allowedRegexParam))
}
}
}
var err error
r.allowRegex, err = regexp.Compile(allowedRegexStr)
if err != nil {
panic(fmt.Errorf("error configuring %s rule: allowedRegex is not valid regex [%s]: %v", r.Name(), allowedRegexStr, err))
}
if r.failureMsg == "" {
r.failureMsg = "parameter '%s' seems to be unused, consider removing or renaming it to match " + r.allowRegex.String()
}
}
// Apply applies the rule to given file.
func (r *UnusedParamRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure {
r.configure(args)
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
w := lintUnusedParamRule{
onFailure: onFailure,
allowRegex: r.allowRegex,
failureMsg: r.failureMsg,
}
ast.Walk(w, file.AST)
return failures
}
// Name returns the rule name.
func (*UnusedParamRule) Name() string {
return "unused-parameter"
}
type lintUnusedParamRule struct {
onFailure func(lint.Failure)
allowRegex *regexp.Regexp
failureMsg string
}
func (w lintUnusedParamRule) Visit(node ast.Node) ast.Visitor {
var (
funcType *ast.FuncType
funcBody *ast.BlockStmt
)
switch n := node.(type) {
case *ast.FuncLit:
funcType = n.Type
funcBody = n.Body
case *ast.FuncDecl:
if n.Body == nil {
return nil // skip, is a function prototype
}
funcType = n.Type
funcBody = n.Body
default:
return w // skip, not a function
}
params := retrieveNamedParams(funcType.Params)
if len(params) < 1 {
return w // skip, func without parameters
}
// inspect the func body looking for references to parameters
fselect := func(n ast.Node) bool {
ident, isAnID := n.(*ast.Ident)
if !isAnID {
return false
}
_, isAParam := params[ident.Obj]
if isAParam {
params[ident.Obj] = false // mark as used
}
return false
}
_ = pick(funcBody, fselect)
for _, p := range funcType.Params.List {
for _, n := range p.Names {
if w.allowRegex.FindStringIndex(n.Name) != nil {
continue
}
if params[n.Obj] {
w.onFailure(lint.Failure{
Confidence: 1,
Node: n,
Category: "bad practice",
Failure: fmt.Sprintf(w.failureMsg, n.Name),
})
}
}
}
return w // full method body was inspected
}
func retrieveNamedParams(params *ast.FieldList) map[*ast.Object]bool {
result := map[*ast.Object]bool{}
if params.List == nil {
return result
}
for _, p := range params.List {
for _, n := range p.Names {
if n.Name == "_" {
continue
}
result[n.Obj] = true
}
}
return result
}