mirror of
https://github.com/mgechev/revive.git
synced 2025-11-23 22:04:49 +02:00
135 lines
3.3 KiB
Go
135 lines
3.3 KiB
Go
package rule
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
|
|
"github.com/mgechev/revive/lint"
|
|
)
|
|
|
|
// EnforceSwitchStyleRule implements a rule to enforce default clauses use and/or position.
|
|
type EnforceSwitchStyleRule struct {
|
|
allowNoDefault bool // allow absence of default
|
|
allowDefaultNotLast bool // allow default, if present, not being the last case
|
|
}
|
|
|
|
// Configure validates the rule configuration, and configures the rule accordingly.
|
|
//
|
|
// Configuration implements the [lint.ConfigurableRule] interface.
|
|
func (r *EnforceSwitchStyleRule) Configure(arguments lint.Arguments) error {
|
|
if len(arguments) < 1 {
|
|
return nil
|
|
}
|
|
|
|
for _, arg := range arguments {
|
|
argStr, ok := arg.(string)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument for rule %s; expected string but got %T", r.Name(), arg)
|
|
}
|
|
switch {
|
|
case isRuleOption(argStr, "allowNoDefault"):
|
|
r.allowNoDefault = true
|
|
case isRuleOption(argStr, "allowDefaultNotLast"):
|
|
r.allowDefaultNotLast = true
|
|
default:
|
|
return fmt.Errorf(`invalid argument %q for rule %s; expected "allowNoDefault" or "allowDefaultNotLast"`, argStr, r.Name())
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Apply applies the rule to given file.
|
|
func (r *EnforceSwitchStyleRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
|
|
var failures []lint.Failure
|
|
astFile := file.AST
|
|
ast.Inspect(astFile, func(n ast.Node) bool {
|
|
switchNode, ok := n.(*ast.SwitchStmt)
|
|
if !ok {
|
|
return true // not a switch statement
|
|
}
|
|
|
|
defaultClause, isLast := r.seekDefaultCase(switchNode.Body)
|
|
hasDefault := defaultClause != nil
|
|
|
|
if !hasDefault && r.allowNoDefault {
|
|
return true // switch without default but the rule is configured to don´t care
|
|
}
|
|
|
|
if !hasDefault && !r.allowNoDefault {
|
|
// switch without default
|
|
if !r.allBranchesEndWithJumpStmt(switchNode) {
|
|
failures = append(failures, lint.Failure{
|
|
Confidence: 1,
|
|
Node: switchNode,
|
|
Category: lint.FailureCategoryStyle,
|
|
Failure: "switch must have a default case clause",
|
|
})
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// the switch has a default
|
|
|
|
if r.allowDefaultNotLast || isLast {
|
|
return true
|
|
}
|
|
|
|
failures = append(failures, lint.Failure{
|
|
Confidence: 1,
|
|
Node: defaultClause,
|
|
Category: lint.FailureCategoryStyle,
|
|
Failure: "default case clause must be the last one",
|
|
})
|
|
|
|
return true
|
|
})
|
|
|
|
return failures
|
|
}
|
|
|
|
func (*EnforceSwitchStyleRule) seekDefaultCase(body *ast.BlockStmt) (defaultClause *ast.CaseClause, isLast bool) {
|
|
var last *ast.CaseClause
|
|
for _, stmt := range body.List {
|
|
cc, _ := stmt.(*ast.CaseClause) // no need to check for ok
|
|
last = cc
|
|
if cc.List == nil { // a nil List means "default"
|
|
defaultClause = cc
|
|
}
|
|
}
|
|
|
|
return defaultClause, defaultClause == last
|
|
}
|
|
|
|
func (*EnforceSwitchStyleRule) allBranchesEndWithJumpStmt(switchStmt *ast.SwitchStmt) bool {
|
|
for _, stmt := range switchStmt.Body.List {
|
|
caseClause := stmt.(*ast.CaseClause) // safe to assume stmt is a case clause
|
|
|
|
caseBody := caseClause.Body
|
|
if caseBody == nil {
|
|
return false
|
|
}
|
|
|
|
lastStmt := caseBody[len(caseBody)-1]
|
|
|
|
if _, ok := lastStmt.(*ast.ReturnStmt); ok {
|
|
continue
|
|
}
|
|
|
|
if jump, ok := lastStmt.(*ast.BranchStmt); ok && jump.Tok == token.BREAK {
|
|
continue
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Name returns the rule name.
|
|
func (*EnforceSwitchStyleRule) Name() string {
|
|
return "enforce-switch-style"
|
|
}
|