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

149 lines
3.6 KiB
Go

package rule
import (
"fmt"
"go/ast"
"github.com/mgechev/revive/internal/astutils"
"github.com/mgechev/revive/lint"
)
// IdenticalIfElseIfConditionsRule warns on if...else if chains with identical conditions.
type IdenticalIfElseIfConditionsRule struct{}
// Apply applies the rule to given file.
func (*IdenticalIfElseIfConditionsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
var failures []lint.Failure
onFailure := func(failure lint.Failure) {
failures = append(failures, failure)
}
getStmtLine := func(s ast.Stmt) int {
return file.ToPosition(s.Pos()).Line
}
w := &rootWalkerIfElseIfIdenticalConditions{getStmtLine: getStmtLine, onFailure: onFailure}
for _, decl := range file.AST.Decls {
fn, ok := decl.(*ast.FuncDecl)
if !ok || fn.Body == nil {
continue
}
ast.Walk(w, fn.Body)
}
return failures
}
// Name returns the rule name.
func (*IdenticalIfElseIfConditionsRule) Name() string {
return "identical-ifelseif-conditions"
}
type rootWalkerIfElseIfIdenticalConditions struct {
getStmtLine func(ast.Stmt) int
onFailure func(lint.Failure)
}
func (w *rootWalkerIfElseIfIdenticalConditions) Visit(node ast.Node) ast.Visitor {
n, ok := node.(*ast.IfStmt)
if !ok {
return w
}
_, isIfElseIf := n.Else.(*ast.IfStmt)
if isIfElseIf {
walker := &lintIfChainIdenticalConditions{
onFailure: w.onFailure,
getStmtLine: w.getStmtLine,
rootWalker: w,
}
ast.Walk(walker, n)
return nil // the walker already analyzed inner branches
}
return w
}
// walkBranch analyzes the given branch.
func (w *rootWalkerIfElseIfIdenticalConditions) walkBranch(branch ast.Stmt) {
if branch == nil {
return
}
walker := &rootWalkerIfElseIfIdenticalConditions{
onFailure: w.onFailure,
getStmtLine: w.getStmtLine,
}
ast.Walk(walker, branch)
}
type lintIfChainIdenticalConditions struct {
getStmtLine func(ast.Stmt) int
onFailure func(lint.Failure)
conditions map[string]int // condition hash -> line of the condition
rootWalker *rootWalkerIfElseIfIdenticalConditions // the walker to use to recursively analyze inner branches
}
// addCondition adds a condition to the set of if...else if conditions.
// If the set already contains the same condition it returns the line number of the identical condition.
func (w *lintIfChainIdenticalConditions) addCondition(condition ast.Expr, conditionLine int) (line int, match bool) {
if condition == nil {
return 0, false
}
if w.conditions == nil {
w.resetConditions()
}
hash := astutils.NodeHash(condition)
identical, ok := w.conditions[hash]
if ok {
return identical, true
}
w.conditions[hash] = conditionLine
return 0, false
}
func (w *lintIfChainIdenticalConditions) resetConditions() {
w.conditions = map[string]int{}
}
func (w *lintIfChainIdenticalConditions) Visit(node ast.Node) ast.Visitor {
n, ok := node.(*ast.IfStmt)
if !ok {
return w
}
// recursively analyze the then-branch
w.rootWalker.walkBranch(n.Body)
if n.Init == nil { // only check if without initialization to avoid false positives
currentCondLine := w.rootWalker.getStmtLine(n)
identicalCondLine, match := w.addCondition(n.Cond, currentCondLine)
if match {
w.onFailure(lint.Failure{
Confidence: 1.0,
Node: n,
Category: lint.FailureCategoryLogic,
Failure: fmt.Sprintf(`"if...else if" chain with identical conditions (lines %d and %d)`, identicalCondLine, currentCondLine),
})
}
}
if n.Else != nil {
if chainedIf, ok := n.Else.(*ast.IfStmt); ok {
w.Visit(chainedIf)
} else {
w.rootWalker.walkBranch(n.Else)
}
}
w.resetConditions()
return nil
}