mirror of
https://github.com/mgechev/revive.git
synced 2024-11-24 08:32:22 +02:00
243 lines
5.1 KiB
Go
243 lines
5.1 KiB
Go
|
package rule
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"go/ast"
|
||
|
"go/token"
|
||
|
|
||
|
"github.com/mgechev/revive/lint"
|
||
|
)
|
||
|
|
||
|
// UnusedParamRule lints unused params in functions.
|
||
|
type UnusedParamRule struct{}
|
||
|
|
||
|
// Apply applies the rule to given file.
|
||
|
func (r *UnusedParamRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
|
||
|
var failures []lint.Failure
|
||
|
|
||
|
onFailure := func(failure lint.Failure) {
|
||
|
failures = append(failures, failure)
|
||
|
}
|
||
|
|
||
|
w := lintUnusedParamRule{onFailure: onFailure}
|
||
|
|
||
|
ast.Walk(w, file.AST)
|
||
|
|
||
|
return failures
|
||
|
}
|
||
|
|
||
|
// Name returns the rule name.
|
||
|
func (r *UnusedParamRule) Name() string {
|
||
|
return "unused-parameter"
|
||
|
}
|
||
|
|
||
|
type lintUnusedParamRule struct {
|
||
|
onFailure func(lint.Failure)
|
||
|
}
|
||
|
|
||
|
func (w lintUnusedParamRule) Visit(node ast.Node) ast.Visitor {
|
||
|
switch n := node.(type) {
|
||
|
case *ast.FuncDecl:
|
||
|
fv := newFuncVisitor(retrieveNamedParams(n.Type.Params.List))
|
||
|
if n.Body != nil {
|
||
|
ast.Walk(fv, n.Body)
|
||
|
checkUnusedParams(w, fv.params, n)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return w
|
||
|
}
|
||
|
|
||
|
type scope struct {
|
||
|
vars map[string]bool
|
||
|
}
|
||
|
|
||
|
func newScope() scope {
|
||
|
return scope{make(map[string]bool, 0)}
|
||
|
}
|
||
|
|
||
|
func (s *scope) addVars(exps []ast.Expr) {
|
||
|
for _, e := range exps {
|
||
|
if id, ok := e.(*ast.Ident); ok {
|
||
|
s.vars[id.Name] = true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type scopeStack struct {
|
||
|
stk []scope
|
||
|
}
|
||
|
|
||
|
func (s *scopeStack) openScope() {
|
||
|
s.stk = append(s.stk, newScope())
|
||
|
}
|
||
|
|
||
|
func (s *scopeStack) closeScope() {
|
||
|
if len(s.stk) > 0 {
|
||
|
s.stk = s.stk[:len(s.stk)-1]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s *scopeStack) currentScope() scope {
|
||
|
if len(s.stk) > 0 {
|
||
|
return s.stk[len(s.stk)-1]
|
||
|
}
|
||
|
|
||
|
panic("no current scope")
|
||
|
}
|
||
|
|
||
|
func newScopeStack() scopeStack {
|
||
|
return scopeStack{make([]scope, 0)}
|
||
|
}
|
||
|
|
||
|
type funcVisitor struct {
|
||
|
sStk scopeStack
|
||
|
params map[string]bool
|
||
|
}
|
||
|
|
||
|
func newFuncVisitor(params map[string]bool) funcVisitor {
|
||
|
return funcVisitor{sStk: newScopeStack(), params: params}
|
||
|
}
|
||
|
|
||
|
func walkStmtList(v ast.Visitor, list []ast.Stmt) {
|
||
|
for _, s := range list {
|
||
|
ast.Walk(v, s)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (v funcVisitor) Visit(node ast.Node) ast.Visitor {
|
||
|
varSelector := func(n ast.Node) bool {
|
||
|
id, ok := n.(*ast.Ident)
|
||
|
return ok && id.Obj != nil && id.Obj.Kind.String() == "var"
|
||
|
}
|
||
|
switch n := node.(type) {
|
||
|
case *ast.BlockStmt:
|
||
|
v.sStk.openScope()
|
||
|
walkStmtList(v, n.List)
|
||
|
v.sStk.closeScope()
|
||
|
return nil
|
||
|
case *ast.AssignStmt:
|
||
|
var uses []ast.Node
|
||
|
if isOpAssign(n.Tok) { // Case of id += expr
|
||
|
uses = append(uses, pickFromExpList(n.Lhs, varSelector, nil)...)
|
||
|
} else { // Case of id[expr] = expr
|
||
|
indexSelector := func(n ast.Node) bool {
|
||
|
_, ok := n.(*ast.IndexExpr)
|
||
|
return ok
|
||
|
}
|
||
|
f := func(n ast.Node) []ast.Node {
|
||
|
ie, ok := n.(*ast.IndexExpr)
|
||
|
if !ok { // not possible
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return pick(ie.Index, varSelector, nil)
|
||
|
}
|
||
|
|
||
|
uses = append(uses, pickFromExpList(n.Lhs, indexSelector, f)...)
|
||
|
}
|
||
|
|
||
|
uses = append(uses, pickFromExpList(n.Rhs, varSelector, nil)...)
|
||
|
|
||
|
markParamListAsUsed(uses, v)
|
||
|
cs := v.sStk.currentScope()
|
||
|
cs.addVars(n.Lhs)
|
||
|
case *ast.Ident:
|
||
|
if n.Obj != nil {
|
||
|
if n.Obj.Kind.String() == "var" {
|
||
|
markParamAsUsed(n, v)
|
||
|
}
|
||
|
}
|
||
|
case *ast.ForStmt:
|
||
|
v.sStk.openScope()
|
||
|
if n.Init != nil {
|
||
|
ast.Walk(v, n.Init)
|
||
|
}
|
||
|
uses := pickFromExpList([]ast.Expr{n.Cond}, varSelector, nil)
|
||
|
markParamListAsUsed(uses, v)
|
||
|
ast.Walk(v, n.Body)
|
||
|
v.sStk.closeScope()
|
||
|
return nil
|
||
|
case *ast.SwitchStmt:
|
||
|
v.sStk.openScope()
|
||
|
if n.Init != nil {
|
||
|
ast.Walk(v, n.Init)
|
||
|
}
|
||
|
uses := pickFromExpList([]ast.Expr{n.Tag}, varSelector, nil)
|
||
|
markParamListAsUsed(uses, v)
|
||
|
// Analyze cases (they are not BlockStmt but a list of Stmt)
|
||
|
cases := n.Body.List
|
||
|
for _, c := range cases {
|
||
|
cc, ok := c.(*ast.CaseClause)
|
||
|
if !ok {
|
||
|
continue
|
||
|
}
|
||
|
uses := pickFromExpList(cc.List, varSelector, nil)
|
||
|
markParamListAsUsed(uses, v)
|
||
|
v.sStk.openScope()
|
||
|
for _, stmt := range cc.Body {
|
||
|
ast.Walk(v, stmt)
|
||
|
}
|
||
|
v.sStk.closeScope()
|
||
|
}
|
||
|
|
||
|
v.sStk.closeScope()
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return v
|
||
|
}
|
||
|
|
||
|
func retrieveNamedParams(pl []*ast.Field) map[string]bool {
|
||
|
result := make(map[string]bool, len(pl))
|
||
|
for _, p := range pl {
|
||
|
for _, n := range p.Names {
|
||
|
if n.Name != "_" {
|
||
|
result[n.Name] = true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return result
|
||
|
}
|
||
|
|
||
|
func checkUnusedParams(w lintUnusedParamRule, params map[string]bool, n *ast.FuncDecl) {
|
||
|
for k, v := range params {
|
||
|
if v {
|
||
|
w.onFailure(lint.Failure{
|
||
|
Confidence: 0.8, // confidence is not 1.0 because of shadow variables
|
||
|
Node: n,
|
||
|
Category: "bad practice",
|
||
|
Failure: fmt.Sprintf("parameter '%s' seems to be unused, consider removing or renaming it as _", k),
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
func markParamListAsUsed(ids []ast.Node, v funcVisitor) {
|
||
|
for _, id := range ids {
|
||
|
markParamAsUsed(id.(*ast.Ident), v)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func markParamAsUsed(id *ast.Ident, v funcVisitor) { // TODO: constraint parameters to receive just a list of params and a scope stack
|
||
|
for _, s := range v.sStk.stk {
|
||
|
if s.vars[id.Name] {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if v.params[id.Name] {
|
||
|
v.params[id.Name] = false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func isOpAssign(aTok token.Token) bool {
|
||
|
return aTok == token.ADD_ASSIGN || aTok == token.AND_ASSIGN ||
|
||
|
aTok == token.MUL_ASSIGN || aTok == token.OR_ASSIGN ||
|
||
|
aTok == token.QUO_ASSIGN || aTok == token.REM_ASSIGN ||
|
||
|
aTok == token.SHL_ASSIGN || aTok == token.SHR_ASSIGN ||
|
||
|
aTok == token.SUB_ASSIGN || aTok == token.XOR_ASSIGN
|
||
|
}
|