mirror of
https://github.com/mgechev/revive.git
synced 2024-11-21 17:16:40 +02:00
ifelse: option to preserve variable scope (#832)
* ifelse: option to preserve variable scope
This commit is contained in:
parent
2a1838f501
commit
ae07914dc4
@ -448,13 +448,13 @@ List of all available rules. The rules ported from `golint` are left unchanged a
|
|||||||
| [`package-comments`](./RULES_DESCRIPTIONS.md#package-comments) | n/a | Package commenting conventions. | yes | no |
|
| [`package-comments`](./RULES_DESCRIPTIONS.md#package-comments) | n/a | Package commenting conventions. | yes | no |
|
||||||
| [`range`](./RULES_DESCRIPTIONS.md#range) | n/a | Prevents redundant variables when iterating over a collection. | yes | no |
|
| [`range`](./RULES_DESCRIPTIONS.md#range) | n/a | Prevents redundant variables when iterating over a collection. | yes | no |
|
||||||
| [`receiver-naming`](./RULES_DESCRIPTIONS.md#receiver-naming) | n/a | Conventions around the naming of receivers. | yes | no |
|
| [`receiver-naming`](./RULES_DESCRIPTIONS.md#receiver-naming) | n/a | Conventions around the naming of receivers. | yes | no |
|
||||||
| [`indent-error-flow`](./RULES_DESCRIPTIONS.md#indent-error-flow) | n/a | Prevents redundant else statements. | yes | no |
|
| [`indent-error-flow`](./RULES_DESCRIPTIONS.md#indent-error-flow) | []string | Prevents redundant else statements. | yes | no |
|
||||||
| [`argument-limit`](./RULES_DESCRIPTIONS.md#argument-limit) | int (defaults to 8) | Specifies the maximum number of arguments a function can receive | no | no |
|
| [`argument-limit`](./RULES_DESCRIPTIONS.md#argument-limit) | int (defaults to 8) | Specifies the maximum number of arguments a function can receive | no | no |
|
||||||
| [`cyclomatic`](./RULES_DESCRIPTIONS.md#cyclomatic) | int (defaults to 10) | Sets restriction for maximum Cyclomatic complexity. | no | no |
|
| [`cyclomatic`](./RULES_DESCRIPTIONS.md#cyclomatic) | int (defaults to 10) | Sets restriction for maximum Cyclomatic complexity. | no | no |
|
||||||
| [`max-public-structs`](./RULES_DESCRIPTIONS.md#max-public-structs) | int (defaults to 5) | The maximum number of public structs in a file. | no | no |
|
| [`max-public-structs`](./RULES_DESCRIPTIONS.md#max-public-structs) | int (defaults to 5) | The maximum number of public structs in a file. | no | no |
|
||||||
| [`file-header`](./RULES_DESCRIPTIONS.md#file-header) | string (defaults to none)| Header which each file should have. | no | no |
|
| [`file-header`](./RULES_DESCRIPTIONS.md#file-header) | string (defaults to none)| Header which each file should have. | no | no |
|
||||||
| [`empty-block`](./RULES_DESCRIPTIONS.md#empty-block) | n/a | Warns on empty code blocks | no | yes |
|
| [`empty-block`](./RULES_DESCRIPTIONS.md#empty-block) | n/a | Warns on empty code blocks | no | yes |
|
||||||
| [`superfluous-else`](./RULES_DESCRIPTIONS.md#superfluous-else) | n/a | Prevents redundant else statements (extends [`indent-error-flow`](./RULES_DESCRIPTIONS.md#indent-error-flow)) | no | no |
|
| [`superfluous-else`](./RULES_DESCRIPTIONS.md#superfluous-else) | []string | Prevents redundant else statements (extends [`indent-error-flow`](./RULES_DESCRIPTIONS.md#indent-error-flow)) | no | no |
|
||||||
| [`confusing-naming`](./RULES_DESCRIPTIONS.md#confusing-naming) | n/a | Warns on methods with names that differ only by capitalization | no | no |
|
| [`confusing-naming`](./RULES_DESCRIPTIONS.md#confusing-naming) | n/a | Warns on methods with names that differ only by capitalization | no | no |
|
||||||
| [`get-return`](./RULES_DESCRIPTIONS.md#get-return) | n/a | Warns on getters that do not yield any result | no | no |
|
| [`get-return`](./RULES_DESCRIPTIONS.md#get-return) | n/a | Warns on getters that do not yield any result | no | no |
|
||||||
| [`modifies-parameter`](./RULES_DESCRIPTIONS.md#modifies-parameter) | n/a | Warns on assignments to function parameters | no | no |
|
| [`modifies-parameter`](./RULES_DESCRIPTIONS.md#modifies-parameter) | n/a | Warns on assignments to function parameters | no | no |
|
||||||
@ -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 (defaults to 7) | Sets restriction for maximum Cognitive complexity. | no | no |
|
| [`cognitive-complexity`](./RULES_DESCRIPTIONS.md#cognitive-complexity) | int (defaults to 7) | 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 where the predicate may be inverted to reduce nesting | no | no |
|
| [`early-return`](./RULES_DESCRIPTIONS.md#early-return) | []string | 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 |
|
||||||
|
@ -299,7 +299,7 @@ if cond {
|
|||||||
```
|
```
|
||||||
where the `if` condition may be inverted in order to reduce nesting:
|
where the `if` condition may be inverted in order to reduce nesting:
|
||||||
```go
|
```go
|
||||||
if ! cond {
|
if !cond {
|
||||||
// do other thing
|
// do other thing
|
||||||
return ...
|
return ...
|
||||||
}
|
}
|
||||||
@ -307,7 +307,16 @@ if ! cond {
|
|||||||
// do something
|
// do something
|
||||||
```
|
```
|
||||||
|
|
||||||
_Configuration_: N/A
|
_Configuration_: ([]string) rule flags. Available flags are:
|
||||||
|
|
||||||
|
* _preserveScope_: do not suggest refactorings that would increase variable scope
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[rule.exported]
|
||||||
|
arguments =["preserveScope"]
|
||||||
|
```
|
||||||
|
|
||||||
## empty-block
|
## empty-block
|
||||||
|
|
||||||
@ -448,7 +457,16 @@ This rule highlights redundant _else-blocks_ that can be eliminated from the cod
|
|||||||
|
|
||||||
More information [here](https://github.com/golang/go/wiki/CodeReviewComments#indent-error-flow)
|
More information [here](https://github.com/golang/go/wiki/CodeReviewComments#indent-error-flow)
|
||||||
|
|
||||||
_Configuration_: N/A
|
_Configuration_: ([]string) rule flags. Available flags are:
|
||||||
|
|
||||||
|
* _preserveScope_: do not suggest refactorings that would increase variable scope
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[rule.exported]
|
||||||
|
arguments =["preserveScope"]
|
||||||
|
```
|
||||||
|
|
||||||
## imports-blacklist
|
## imports-blacklist
|
||||||
|
|
||||||
@ -624,7 +642,16 @@ To accept the `inline` option in JSON tags (and `outline` and `gnu` in BSON tags
|
|||||||
_Description_: To improve the readability of code, it is recommended to reduce the indentation as much as possible.
|
_Description_: To improve the readability of code, it is recommended to reduce the indentation as much as possible.
|
||||||
This rule highlights redundant _else-blocks_ that can be eliminated from the code.
|
This rule highlights redundant _else-blocks_ that can be eliminated from the code.
|
||||||
|
|
||||||
_Configuration_: N/A
|
_Configuration_: ([]string) rule flags. Available flags are:
|
||||||
|
|
||||||
|
* _preserveScope_: do not suggest refactorings that would increase variable scope
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[rule.exported]
|
||||||
|
arguments =["preserveScope"]
|
||||||
|
```
|
||||||
|
|
||||||
## time-equal
|
## time-equal
|
||||||
|
|
||||||
|
11
internal/ifelse/args.go
Normal file
11
internal/ifelse/args.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package ifelse
|
||||||
|
|
||||||
|
// PreserveScope is a configuration argument that prevents suggestions
|
||||||
|
// that would enlarge variable scope
|
||||||
|
const PreserveScope = "preserveScope"
|
||||||
|
|
||||||
|
// Args contains arguments common to the early-return, indent-error-flow
|
||||||
|
// and superfluous-else rules (currently just preserveScope)
|
||||||
|
type Args struct {
|
||||||
|
PreserveScope bool
|
||||||
|
}
|
@ -9,29 +9,37 @@ import (
|
|||||||
// Branch contains information about a branch within an if-else chain.
|
// Branch contains information about a branch within an if-else chain.
|
||||||
type Branch struct {
|
type Branch struct {
|
||||||
BranchKind
|
BranchKind
|
||||||
Call // The function called at the end for kind Panic or Exit.
|
Call // The function called at the end for kind Panic or Exit.
|
||||||
|
HasDecls bool // The branch has one or more declarations (at the top level block)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlockBranch gets the Branch of an ast.BlockStmt.
|
// BlockBranch gets the Branch of an ast.BlockStmt.
|
||||||
func BlockBranch(block *ast.BlockStmt) Branch {
|
func BlockBranch(block *ast.BlockStmt) Branch {
|
||||||
blockLen := len(block.List)
|
blockLen := len(block.List)
|
||||||
if blockLen == 0 {
|
if blockLen == 0 {
|
||||||
return Branch{BranchKind: Empty}
|
return Empty.Branch()
|
||||||
}
|
}
|
||||||
|
|
||||||
switch stmt := block.List[blockLen-1].(type) {
|
branch := StmtBranch(block.List[blockLen-1])
|
||||||
|
branch.HasDecls = hasDecls(block)
|
||||||
|
return branch
|
||||||
|
}
|
||||||
|
|
||||||
|
// StmtBranch gets the Branch of an ast.Stmt.
|
||||||
|
func StmtBranch(stmt ast.Stmt) Branch {
|
||||||
|
switch stmt := stmt.(type) {
|
||||||
case *ast.ReturnStmt:
|
case *ast.ReturnStmt:
|
||||||
return Branch{BranchKind: Return}
|
return Return.Branch()
|
||||||
case *ast.BlockStmt:
|
case *ast.BlockStmt:
|
||||||
return BlockBranch(stmt)
|
return BlockBranch(stmt)
|
||||||
case *ast.BranchStmt:
|
case *ast.BranchStmt:
|
||||||
switch stmt.Tok {
|
switch stmt.Tok {
|
||||||
case token.BREAK:
|
case token.BREAK:
|
||||||
return Branch{BranchKind: Break}
|
return Break.Branch()
|
||||||
case token.CONTINUE:
|
case token.CONTINUE:
|
||||||
return Branch{BranchKind: Continue}
|
return Continue.Branch()
|
||||||
case token.GOTO:
|
case token.GOTO:
|
||||||
return Branch{BranchKind: Goto}
|
return Goto.Branch()
|
||||||
}
|
}
|
||||||
case *ast.ExprStmt:
|
case *ast.ExprStmt:
|
||||||
fn, ok := ExprCall(stmt)
|
fn, ok := ExprCall(stmt)
|
||||||
@ -42,9 +50,12 @@ func BlockBranch(block *ast.BlockStmt) Branch {
|
|||||||
if ok {
|
if ok {
|
||||||
return Branch{BranchKind: kind, Call: fn}
|
return Branch{BranchKind: kind, Call: fn}
|
||||||
}
|
}
|
||||||
|
case *ast.EmptyStmt:
|
||||||
|
return Empty.Branch()
|
||||||
|
case *ast.LabeledStmt:
|
||||||
|
return StmtBranch(stmt.Stmt)
|
||||||
}
|
}
|
||||||
|
return Regular.Branch()
|
||||||
return Branch{BranchKind: Regular}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a brief string representation
|
// String returns a brief string representation
|
||||||
@ -66,3 +77,17 @@ func (b Branch) LongString() string {
|
|||||||
return b.BranchKind.LongString()
|
return b.BranchKind.LongString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasDecls(block *ast.BlockStmt) bool {
|
||||||
|
for _, stmt := range block.List {
|
||||||
|
switch stmt := stmt.(type) {
|
||||||
|
case *ast.DeclStmt:
|
||||||
|
return true
|
||||||
|
case *ast.AssignStmt:
|
||||||
|
if stmt.Tok == token.DEFINE {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -49,6 +49,9 @@ func (k BranchKind) Deviates() bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Branch returns a Branch with the given kind
|
||||||
|
func (k BranchKind) Branch() Branch { return Branch{BranchKind: k} }
|
||||||
|
|
||||||
// String returns a brief string representation
|
// String returns a brief string representation
|
||||||
func (k BranchKind) String() string {
|
func (k BranchKind) String() string {
|
||||||
switch k {
|
switch k {
|
||||||
|
@ -6,4 +6,5 @@ type Chain struct {
|
|||||||
Else Branch // what happens at the end of the "else" block
|
Else Branch // what happens at the end of the "else" block
|
||||||
HasInitializer bool // is there an "if"-initializer somewhere in the chain?
|
HasInitializer bool // is there an "if"-initializer somewhere in the chain?
|
||||||
HasPriorNonDeviating bool // is there a prior "if" block that does NOT deviate control flow?
|
HasPriorNonDeviating bool // is there a prior "if" block that does NOT deviate control flow?
|
||||||
|
AtBlockEnd bool // whether the chain is placed at the end of the surrounding block
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
// Rule is an interface for linters operating on if-else chains
|
// Rule is an interface for linters operating on if-else chains
|
||||||
type Rule interface {
|
type Rule interface {
|
||||||
CheckIfElse(chain Chain) (failMsg string)
|
CheckIfElse(chain Chain, args Args) (failMsg string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply evaluates the given Rule on if-else chains found within the given AST,
|
// Apply evaluates the given Rule on if-else chains found within the given AST,
|
||||||
@ -28,8 +28,13 @@ type Rule interface {
|
|||||||
//
|
//
|
||||||
// Only the block following "bar" is linted. This is because the rules that use this function
|
// Only the block following "bar" is linted. This is because the rules that use this function
|
||||||
// do not presently have anything to say about earlier blocks in the chain.
|
// do not presently have anything to say about earlier blocks in the chain.
|
||||||
func Apply(rule Rule, node ast.Node, target Target) []lint.Failure {
|
func Apply(rule Rule, node ast.Node, target Target, args lint.Arguments) []lint.Failure {
|
||||||
v := &visitor{rule: rule, target: target}
|
v := &visitor{rule: rule, target: target}
|
||||||
|
for _, arg := range args {
|
||||||
|
if arg == PreserveScope {
|
||||||
|
v.args.PreserveScope = true
|
||||||
|
}
|
||||||
|
}
|
||||||
ast.Walk(v, node)
|
ast.Walk(v, node)
|
||||||
return v.failures
|
return v.failures
|
||||||
}
|
}
|
||||||
@ -38,15 +43,22 @@ type visitor struct {
|
|||||||
failures []lint.Failure
|
failures []lint.Failure
|
||||||
target Target
|
target Target
|
||||||
rule Rule
|
rule Rule
|
||||||
|
args Args
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *visitor) Visit(node ast.Node) ast.Visitor {
|
func (v *visitor) Visit(node ast.Node) ast.Visitor {
|
||||||
ifStmt, ok := node.(*ast.IfStmt)
|
block, ok := node.(*ast.BlockStmt)
|
||||||
if !ok {
|
if !ok {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
v.visitChain(ifStmt, Chain{})
|
for i, stmt := range block.List {
|
||||||
|
if ifStmt, ok := stmt.(*ast.IfStmt); ok {
|
||||||
|
v.visitChain(ifStmt, Chain{AtBlockEnd: i == len(block.List)-1})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ast.Walk(v, stmt)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,8 +85,9 @@ func (v *visitor) visitChain(ifStmt *ast.IfStmt, chain Chain) {
|
|||||||
case *ast.BlockStmt:
|
case *ast.BlockStmt:
|
||||||
// look for other if-else chains nested inside this else { } block
|
// look for other if-else chains nested inside this else { } block
|
||||||
ast.Walk(v, elseBlock)
|
ast.Walk(v, elseBlock)
|
||||||
|
|
||||||
chain.Else = BlockBranch(elseBlock)
|
chain.Else = BlockBranch(elseBlock)
|
||||||
if failMsg := v.rule.CheckIfElse(chain); failMsg != "" {
|
if failMsg := v.rule.CheckIfElse(chain, v.args); failMsg != "" {
|
||||||
if chain.HasInitializer {
|
if chain.HasInitializer {
|
||||||
// if statement has a := initializer, so we might need to move the assignment
|
// if statement has a := initializer, so we might need to move the assignment
|
||||||
// onto its own line in case the body references it
|
// onto its own line in case the body references it
|
||||||
|
@ -12,8 +12,8 @@ import (
|
|||||||
type EarlyReturnRule struct{}
|
type EarlyReturnRule struct{}
|
||||||
|
|
||||||
// Apply applies the rule to given file.
|
// Apply applies the rule to given file.
|
||||||
func (e *EarlyReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
|
func (e *EarlyReturnRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure {
|
||||||
return ifelse.Apply(e, file.AST, ifelse.TargetIf)
|
return ifelse.Apply(e, file.AST, ifelse.TargetIf, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the rule name.
|
// Name returns the rule name.
|
||||||
@ -22,7 +22,7 @@ func (*EarlyReturnRule) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CheckIfElse evaluates the rule against an ifelse.Chain.
|
// CheckIfElse evaluates the rule against an ifelse.Chain.
|
||||||
func (e *EarlyReturnRule) CheckIfElse(chain ifelse.Chain) (failMsg string) {
|
func (e *EarlyReturnRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) {
|
||||||
if !chain.Else.Deviates() {
|
if !chain.Else.Deviates() {
|
||||||
// this rule only applies if the else-block deviates control flow
|
// this rule only applies if the else-block deviates control flow
|
||||||
return
|
return
|
||||||
@ -39,6 +39,11 @@ func (e *EarlyReturnRule) CheckIfElse(chain ifelse.Chain) (failMsg string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if args.PreserveScope && !chain.AtBlockEnd && (chain.HasInitializer || chain.If.HasDecls) {
|
||||||
|
// avoid increasing variable scope
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if chain.If.IsEmpty() {
|
if chain.If.IsEmpty() {
|
||||||
return fmt.Sprintf("if c { } else { %[1]v } can be simplified to if !c { %[1]v }", chain.Else)
|
return fmt.Sprintf("if c { } else { %[1]v } can be simplified to if !c { %[1]v }", chain.Else)
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,8 @@ import (
|
|||||||
type IndentErrorFlowRule struct{}
|
type IndentErrorFlowRule struct{}
|
||||||
|
|
||||||
// Apply applies the rule to given file.
|
// Apply applies the rule to given file.
|
||||||
func (e *IndentErrorFlowRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
|
func (e *IndentErrorFlowRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure {
|
||||||
return ifelse.Apply(e, file.AST, ifelse.TargetElse)
|
return ifelse.Apply(e, file.AST, ifelse.TargetElse, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the rule name.
|
// Name returns the rule name.
|
||||||
@ -19,7 +19,7 @@ func (*IndentErrorFlowRule) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CheckIfElse evaluates the rule against an ifelse.Chain.
|
// CheckIfElse evaluates the rule against an ifelse.Chain.
|
||||||
func (e *IndentErrorFlowRule) CheckIfElse(chain ifelse.Chain) (failMsg string) {
|
func (e *IndentErrorFlowRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) {
|
||||||
if !chain.If.Deviates() {
|
if !chain.If.Deviates() {
|
||||||
// this rule only applies if the if-block deviates control flow
|
// this rule only applies if the if-block deviates control flow
|
||||||
return
|
return
|
||||||
@ -36,5 +36,10 @@ func (e *IndentErrorFlowRule) CheckIfElse(chain ifelse.Chain) (failMsg string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if args.PreserveScope && !chain.AtBlockEnd && (chain.HasInitializer || chain.Else.HasDecls) {
|
||||||
|
// avoid increasing variable scope
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return "if block ends with a return statement, so drop this else and outdent its block"
|
return "if block ends with a return statement, so drop this else and outdent its block"
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@ import (
|
|||||||
type SuperfluousElseRule struct{}
|
type SuperfluousElseRule struct{}
|
||||||
|
|
||||||
// Apply applies the rule to given file.
|
// Apply applies the rule to given file.
|
||||||
func (e *SuperfluousElseRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
|
func (e *SuperfluousElseRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure {
|
||||||
return ifelse.Apply(e, file.AST, ifelse.TargetElse)
|
return ifelse.Apply(e, file.AST, ifelse.TargetElse, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the rule name.
|
// Name returns the rule name.
|
||||||
@ -20,7 +20,7 @@ func (*SuperfluousElseRule) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CheckIfElse evaluates the rule against an ifelse.Chain.
|
// CheckIfElse evaluates the rule against an ifelse.Chain.
|
||||||
func (e *SuperfluousElseRule) CheckIfElse(chain ifelse.Chain) (failMsg string) {
|
func (e *SuperfluousElseRule) CheckIfElse(chain ifelse.Chain, args ifelse.Args) (failMsg string) {
|
||||||
if !chain.If.Deviates() {
|
if !chain.If.Deviates() {
|
||||||
// this rule only applies if the if-block deviates control flow
|
// this rule only applies if the if-block deviates control flow
|
||||||
return
|
return
|
||||||
@ -37,5 +37,10 @@ func (e *SuperfluousElseRule) CheckIfElse(chain ifelse.Chain) (failMsg string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if args.PreserveScope && !chain.AtBlockEnd && (chain.HasInitializer || chain.Else.HasDecls) {
|
||||||
|
// avoid increasing variable scope
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("if block ends with %v, so drop this else and outdent its block", chain.If.LongString())
|
return fmt.Sprintf("if block ends with %v, so drop this else and outdent its block", chain.If.LongString())
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,13 @@ package test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mgechev/revive/internal/ifelse"
|
||||||
|
"github.com/mgechev/revive/lint"
|
||||||
"github.com/mgechev/revive/rule"
|
"github.com/mgechev/revive/rule"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestEarlyReturn tests early-return rule.
|
// TestEarlyReturn tests early-return rule.
|
||||||
func TestEarlyReturn(t *testing.T) {
|
func TestEarlyReturn(t *testing.T) {
|
||||||
testRule(t, "early-return", &rule.EarlyReturnRule{})
|
testRule(t, "early-return", &rule.EarlyReturnRule{})
|
||||||
|
testRule(t, "early-return-scope", &rule.EarlyReturnRule{}, &lint.RuleConfig{Arguments: []interface{}{ifelse.PreserveScope}})
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,13 @@ package test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mgechev/revive/internal/ifelse"
|
||||||
|
"github.com/mgechev/revive/lint"
|
||||||
"github.com/mgechev/revive/rule"
|
"github.com/mgechev/revive/rule"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestSuperfluousElse rule.
|
// TestSuperfluousElse rule.
|
||||||
func TestSuperfluousElse(t *testing.T) {
|
func TestSuperfluousElse(t *testing.T) {
|
||||||
testRule(t, "superfluous-else", &rule.SuperfluousElseRule{})
|
testRule(t, "superfluous-else", &rule.SuperfluousElseRule{})
|
||||||
|
testRule(t, "superfluous-else-scope", &rule.SuperfluousElseRule{}, &lint.RuleConfig{Arguments: []interface{}{ifelse.PreserveScope}})
|
||||||
}
|
}
|
||||||
|
66
testdata/early-return-scope.go
vendored
Normal file
66
testdata/early-return-scope.go
vendored
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Test data for the early-return rule with preserveScope option enabled
|
||||||
|
|
||||||
|
package fixtures
|
||||||
|
|
||||||
|
func fn1() {
|
||||||
|
// No initializer, match as normal
|
||||||
|
if cond { // MATCH /if c { ... } else { ... return } can be simplified to if !c { ... return } .../
|
||||||
|
fn2()
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fn2() {
|
||||||
|
// Moving the declaration of x here is fine since it goes out of scope either way
|
||||||
|
if x := fn1(); x != nil { // MATCH /if c { ... } else { ... return } can be simplified to if !c { ... return } ... (move short variable declaration to its own line if necessary)/
|
||||||
|
fn2()
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fn3() {
|
||||||
|
// Don't want to move the declaration of x here since it stays in scope afterward
|
||||||
|
if x := fn1(); x != nil {
|
||||||
|
fn2()
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
x := fn2()
|
||||||
|
fn3(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fn4() {
|
||||||
|
if cond {
|
||||||
|
var x = fn2()
|
||||||
|
fn3(x)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Don't want to move the declaration of x here since it stays in scope afterward
|
||||||
|
y := fn2()
|
||||||
|
fn3(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fn5() {
|
||||||
|
if cond {
|
||||||
|
x := fn2()
|
||||||
|
fn3(x)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Don't want to move the declaration of x here since it stays in scope afterward
|
||||||
|
y := fn2()
|
||||||
|
fn3(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fn6() {
|
||||||
|
if cond { // MATCH /if c { ... } else { ... return } can be simplified to if !c { ... return } .../
|
||||||
|
x := fn2()
|
||||||
|
fn3(x)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Moving x here is fine since it goes out of scope anyway
|
||||||
|
}
|
64
testdata/superfluous-else-scope.go
vendored
Normal file
64
testdata/superfluous-else-scope.go
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Test data for the superfluous-else rule with preserveScope option enabled
|
||||||
|
|
||||||
|
package fixtures
|
||||||
|
|
||||||
|
func fn1() {
|
||||||
|
for {
|
||||||
|
// No initializer, match as normal
|
||||||
|
if cond {
|
||||||
|
continue
|
||||||
|
} else { // MATCH /if block ends with a continue statement, so drop this else and outdent its block/
|
||||||
|
fn2()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fn2() {
|
||||||
|
for {
|
||||||
|
// Moving the declaration of x here is fine since it goes out of scope either way
|
||||||
|
if x := fn1(); x != nil {
|
||||||
|
continue
|
||||||
|
} else { // MATCH /if block ends with a continue statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)/
|
||||||
|
fn2()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fn3() {
|
||||||
|
for {
|
||||||
|
// Don't want to move the declaration of x here since it stays in scope afterward
|
||||||
|
if x := fn1(); x != nil {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
fn2()
|
||||||
|
}
|
||||||
|
x := fn2()
|
||||||
|
fn3(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fn4() {
|
||||||
|
for {
|
||||||
|
if cond {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
x := fn1()
|
||||||
|
fn2(x)
|
||||||
|
}
|
||||||
|
// Don't want to move the declaration of x here since it stays in scope afterward
|
||||||
|
y := fn2()
|
||||||
|
fn3(y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fn4() {
|
||||||
|
for {
|
||||||
|
if cond {
|
||||||
|
continue
|
||||||
|
} else { // MATCH /if block ends with a continue statement, so drop this else and outdent its block/
|
||||||
|
x := fn1()
|
||||||
|
fn2(x)
|
||||||
|
}
|
||||||
|
// Moving x here is fine since it goes out of scope anyway
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user