mirror of
https://github.com/mgechev/revive.git
synced 2025-04-21 11:56:55 +02:00
Fix early-return false positive and other tweaks (#776)
This commit is contained in:
parent
d5d9da17ec
commit
b87d391ee4
@ -487,7 +487,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a
|
|||||||
| [`cognitive-complexity`](./RULES_DESCRIPTIONS.md#cognitive-complexity) | int | Sets restriction for maximum Cognitive complexity. | no | no |
|
| [`cognitive-complexity`](./RULES_DESCRIPTIONS.md#cognitive-complexity) | int | Sets restriction for maximum Cognitive complexity. | no | no |
|
||||||
| [`string-of-int`](./RULES_DESCRIPTIONS.md#string-of-int) | n/a | Warns on suspicious casts from int to string | no | yes |
|
| [`string-of-int`](./RULES_DESCRIPTIONS.md#string-of-int) | n/a | Warns on suspicious casts from int to string | no | yes |
|
||||||
| [`string-format`](./RULES_DESCRIPTIONS.md#string-format) | map | Warns on specific string literals that fail one or more user-configured regular expressions | no | no |
|
| [`string-format`](./RULES_DESCRIPTIONS.md#string-format) | map | Warns on specific string literals that fail one or more user-configured regular expressions | no | no |
|
||||||
| [`early-return`](./RULES_DESCRIPTIONS.md#early-return) | n/a | Spots if-then-else statements that can be refactored to simplify code reading | no | no |
|
| [`early-return`](./RULES_DESCRIPTIONS.md#early-return) | n/a | Spots if-then-else statements where the predicate may be inverted to reduce nesting | no | no |
|
||||||
| [`unconditional-recursion`](./RULES_DESCRIPTIONS.md#unconditional-recursion) | n/a | Warns on function calls that will lead to (direct) infinite recursion | no | no |
|
| [`unconditional-recursion`](./RULES_DESCRIPTIONS.md#unconditional-recursion) | n/a | Warns on function calls that will lead to (direct) infinite recursion | no | no |
|
||||||
| [`identical-branches`](./RULES_DESCRIPTIONS.md#identical-branches) | n/a | Spots if-then-else statements with identical `then` and `else` branches | no | no |
|
| [`identical-branches`](./RULES_DESCRIPTIONS.md#identical-branches) | n/a | Spots if-then-else statements with identical `then` and `else` branches | no | no |
|
||||||
| [`defer`](./RULES_DESCRIPTIONS.md#defer) | map | Warns on some [defer gotchas](https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-iii-36a1ab3d6ef1) | no | no |
|
| [`defer`](./RULES_DESCRIPTIONS.md#defer) | map | Warns on some [defer gotchas](https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-iii-36a1ab3d6ef1) | no | no |
|
||||||
|
@ -288,7 +288,7 @@ _Configuration_: N/A
|
|||||||
|
|
||||||
## early-return
|
## early-return
|
||||||
|
|
||||||
_Description_: In GO it is idiomatic to minimize nesting statements, a typical example is to avoid if-then-else constructions. This rule spots constructions like
|
_Description_: In Go it is idiomatic to minimize nesting statements, a typical example is to avoid if-then-else constructions. This rule spots constructions like
|
||||||
```go
|
```go
|
||||||
if cond {
|
if cond {
|
||||||
// do something
|
// do something
|
||||||
@ -297,7 +297,7 @@ if cond {
|
|||||||
return ...
|
return ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
that can be rewritten into more idiomatic:
|
where the `if` condition may be inverted in order to reduce nesting:
|
||||||
```go
|
```go
|
||||||
if ! cond {
|
if ! cond {
|
||||||
// do other thing
|
// do other thing
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
package rule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
|
||||||
"github.com/mgechev/revive/lint"
|
"github.com/mgechev/revive/lint"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EarlyReturnRule lints given else constructs.
|
// EarlyReturnRule finds opportunities to reduce nesting by inverting
|
||||||
|
// the condition of an "if" block.
|
||||||
type EarlyReturnRule struct{}
|
type EarlyReturnRule struct{}
|
||||||
|
|
||||||
// Apply applies the rule to given file.
|
// Apply applies the rule to given file.
|
||||||
@ -32,47 +35,142 @@ type lintEarlyReturnRule struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w lintEarlyReturnRule) Visit(node ast.Node) ast.Visitor {
|
func (w lintEarlyReturnRule) Visit(node ast.Node) ast.Visitor {
|
||||||
switch n := node.(type) {
|
ifStmt, ok := node.(*ast.IfStmt)
|
||||||
case *ast.IfStmt:
|
|
||||||
if n.Else == nil {
|
|
||||||
// no else branch
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
elseBlock, ok := n.Else.(*ast.BlockStmt)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
// is if-else-if
|
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
lenElseBlock := len(elseBlock.List)
|
w.visitIf(ifStmt, false, false)
|
||||||
if lenElseBlock < 1 {
|
return nil
|
||||||
// empty else block, continue (there is another rule that warns on empty blocks)
|
}
|
||||||
return w
|
|
||||||
}
|
func (w lintEarlyReturnRule) visitIf(ifStmt *ast.IfStmt, hasNonReturnBranch, hasIfInitializer bool) {
|
||||||
|
// look for other if-else chains nested inside this if { } block
|
||||||
lenThenBlock := len(n.Body.List)
|
ast.Walk(w, ifStmt.Body)
|
||||||
if lenThenBlock < 1 {
|
|
||||||
// then block is empty thus the stmt can be simplified
|
if ifStmt.Else == nil {
|
||||||
w.onFailure(lint.Failure{
|
// no else branch
|
||||||
Confidence: 1,
|
return
|
||||||
Node: n,
|
}
|
||||||
Failure: "if c { } else {... return} can be simplified to if !c { ... return }",
|
|
||||||
})
|
if as, ok := ifStmt.Init.(*ast.AssignStmt); ok && as.Tok == token.DEFINE {
|
||||||
|
hasIfInitializer = true
|
||||||
return w
|
}
|
||||||
}
|
bodyFlow := w.branchFlow(ifStmt.Body)
|
||||||
|
|
||||||
_, lastThenStmtIsReturn := n.Body.List[lenThenBlock-1].(*ast.ReturnStmt)
|
switch elseBlock := ifStmt.Else.(type) {
|
||||||
_, lastElseStmtIsReturn := elseBlock.List[lenElseBlock-1].(*ast.ReturnStmt)
|
case *ast.IfStmt:
|
||||||
if lastElseStmtIsReturn && !lastThenStmtIsReturn {
|
if bodyFlow.canFlowIntoNext() {
|
||||||
w.onFailure(lint.Failure{
|
hasNonReturnBranch = true
|
||||||
Confidence: 1,
|
}
|
||||||
Node: n,
|
w.visitIf(elseBlock, hasNonReturnBranch, hasIfInitializer)
|
||||||
Failure: "if c {...} else {... return } can be simplified to if !c { ... return } ...",
|
|
||||||
})
|
case *ast.BlockStmt:
|
||||||
}
|
// look for other if-else chains nested inside this else { } block
|
||||||
}
|
ast.Walk(w, elseBlock)
|
||||||
|
|
||||||
return w
|
if hasNonReturnBranch && bodyFlow != branchFlowEmpty {
|
||||||
|
// if we de-indent this block then a previous branch
|
||||||
|
// might flow into it, affecting program behaviour
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bodyFlow.canFlowIntoNext() {
|
||||||
|
// avoid overlapping with superfluous-else
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
elseFlow := w.branchFlow(elseBlock)
|
||||||
|
if !elseFlow.canFlowIntoNext() {
|
||||||
|
failMsg := fmt.Sprintf("if c {%[1]s } else {%[2]s } can be simplified to if !c {%[2]s }%[1]s",
|
||||||
|
bodyFlow, elseFlow)
|
||||||
|
|
||||||
|
if hasIfInitializer {
|
||||||
|
// if statement has a := initializer, so we might need to move the assignment
|
||||||
|
// onto its own line in case the body references it
|
||||||
|
failMsg += " (move short variable declaration to its own line if necessary)"
|
||||||
|
}
|
||||||
|
|
||||||
|
w.onFailure(lint.Failure{
|
||||||
|
Confidence: 1,
|
||||||
|
Node: ifStmt,
|
||||||
|
Failure: failMsg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("invalid node type for else")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type branchFlowKind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
branchFlowEmpty branchFlowKind = iota
|
||||||
|
branchFlowReturn
|
||||||
|
branchFlowPanic
|
||||||
|
branchFlowContinue
|
||||||
|
branchFlowBreak
|
||||||
|
branchFlowGoto
|
||||||
|
branchFlowRegular
|
||||||
|
)
|
||||||
|
|
||||||
|
func (w lintEarlyReturnRule) branchFlow(block *ast.BlockStmt) branchFlowKind {
|
||||||
|
blockLen := len(block.List)
|
||||||
|
if blockLen == 0 {
|
||||||
|
return branchFlowEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
switch stmt := block.List[blockLen-1].(type) {
|
||||||
|
case *ast.ReturnStmt:
|
||||||
|
return branchFlowReturn
|
||||||
|
case *ast.BlockStmt:
|
||||||
|
return w.branchFlow(stmt)
|
||||||
|
case *ast.BranchStmt:
|
||||||
|
switch stmt.Tok {
|
||||||
|
case token.BREAK:
|
||||||
|
return branchFlowBreak
|
||||||
|
case token.CONTINUE:
|
||||||
|
return branchFlowContinue
|
||||||
|
case token.GOTO:
|
||||||
|
return branchFlowGoto
|
||||||
|
}
|
||||||
|
case *ast.ExprStmt:
|
||||||
|
if call, ok := stmt.X.(*ast.CallExpr); ok && isIdent(call.Fun, "panic") {
|
||||||
|
return branchFlowPanic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return branchFlowRegular
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether this branch's control can flow into the next statement following the if-else chain
|
||||||
|
func (k branchFlowKind) canFlowIntoNext() bool {
|
||||||
|
switch k {
|
||||||
|
case branchFlowReturn, branchFlowPanic, branchFlowContinue, branchFlowBreak, branchFlowGoto:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k branchFlowKind) String() string {
|
||||||
|
switch k {
|
||||||
|
case branchFlowEmpty:
|
||||||
|
return ""
|
||||||
|
case branchFlowReturn:
|
||||||
|
return " ... return"
|
||||||
|
case branchFlowPanic:
|
||||||
|
return " ... panic()"
|
||||||
|
case branchFlowContinue:
|
||||||
|
return " ... continue"
|
||||||
|
case branchFlowBreak:
|
||||||
|
return " ... break"
|
||||||
|
case branchFlowGoto:
|
||||||
|
return " ... goto"
|
||||||
|
case branchFlowRegular:
|
||||||
|
return " ..."
|
||||||
|
default:
|
||||||
|
panic("invalid kind")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
77
testdata/early-return.go
vendored
77
testdata/early-return.go
vendored
@ -3,7 +3,7 @@
|
|||||||
package fixtures
|
package fixtures
|
||||||
|
|
||||||
func earlyRet() bool {
|
func earlyRet() bool {
|
||||||
if cond { // MATCH /if c {...} else {... return } can be simplified to if !c { ... return } .../
|
if cond { // MATCH /if c { ... } else { ... return } can be simplified to if !c { ... return } .../
|
||||||
println()
|
println()
|
||||||
println()
|
println()
|
||||||
println()
|
println()
|
||||||
@ -11,27 +11,28 @@ func earlyRet() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if cond { //MATCH /if c {...} else {... return } can be simplified to if !c { ... return } .../
|
if cond { //MATCH /if c { ... } else { ... return } can be simplified to if !c { ... return } .../
|
||||||
println()
|
println()
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if cond { //MATCH /if c { } else {... return} can be simplified to if !c { ... return }/
|
if cond { //MATCH /if c { } else { ... return } can be simplified to if !c { ... return }/
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if cond {
|
if cond {
|
||||||
println()
|
println()
|
||||||
} else if cond { //MATCH /if c { } else {... return} can be simplified to if !c { ... return }/
|
} else if cond { //MATCH /if c { } else { ... return } can be simplified to if !c { ... return }/
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the first branch does not return, so we can't reduce nesting here
|
||||||
if cond {
|
if cond {
|
||||||
println()
|
println()
|
||||||
} else if cond { //MATCH /if c {...} else {... return } can be simplified to if !c { ... return } .../
|
} else if cond {
|
||||||
println()
|
println()
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -44,7 +45,7 @@ func earlyRet() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if cond { //MATCH /if c {...} else {... return } can be simplified to if !c { ... return } .../
|
if cond { //MATCH /if c { ... } else { ... return } can be simplified to if !c { ... return } .../
|
||||||
println()
|
println()
|
||||||
println()
|
println()
|
||||||
println()
|
println()
|
||||||
@ -59,4 +60,68 @@ func earlyRet() bool {
|
|||||||
} else {
|
} else {
|
||||||
println()
|
println()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cond {
|
||||||
|
if cond { //MATCH /if c { ... } else { ... return } can be simplified to if !c { ... return } .../
|
||||||
|
println()
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cond {
|
||||||
|
println()
|
||||||
|
} else {
|
||||||
|
if cond { //MATCH /if c { ... } else { ... return } can be simplified to if !c { ... return } .../
|
||||||
|
println()
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cond {
|
||||||
|
println()
|
||||||
|
} else if cond {
|
||||||
|
println()
|
||||||
|
} else {
|
||||||
|
if cond { //MATCH /if c { ... } else { ... return } can be simplified to if !c { ... return } .../
|
||||||
|
println()
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if cond { //MATCH /if c { ... } else { ... continue } can be simplified to if !c { ... continue } .../
|
||||||
|
println()
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if cond { //MATCH /if c { ... } else { ... break } can be simplified to if !c { ... break } .../
|
||||||
|
println()
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cond { //MATCH /if c { ... } else { ... panic() } can be simplified to if !c { ... panic() } .../
|
||||||
|
println()
|
||||||
|
} else {
|
||||||
|
panic("!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cond { //MATCH /if c { ... } else { ... goto } can be simplified to if !c { ... goto } .../
|
||||||
|
println()
|
||||||
|
} else {
|
||||||
|
goto X
|
||||||
|
}
|
||||||
|
|
||||||
|
if x, ok := foo(); ok { //MATCH /if c { ... } else { ... return } can be simplified to if !c { ... return } ... (move short variable declaration to its own line if necessary)/
|
||||||
|
println(x)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user