mirror of
https://github.com/mgechev/revive.git
synced 2025-11-23 22:04:49 +02:00
149 lines
3.6 KiB
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
|
|
}
|