Skip to content

Commit c939bb6

Browse files
authored
add new rule useless-break (#551)
1 parent 98c374d commit c939bb6

File tree

6 files changed

+189
-0
lines changed

6 files changed

+189
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a
453453
| [`unexported-naming`](./RULES_DESCRIPTIONS.md#unexported-naming) | n/a | Warns on wrongly named un-exported symbols | no | no |
454454
| [`function-length`](./RULES_DESCRIPTIONS.md#function-length) | n/a | Warns on functions exceeding the statements or lines max | no | no |
455455
| [`nested-structs`](./RULES_DESCRIPTIONS.md#nested-structs) | n/a | Warns on structs within structs | no | no |
456+
| [`useless-break`](./RULES_DESCRIPTIONS.md#useless-break) | n/a | Warns on useless `break` statements in case clauses | no | no |
456457

457458
## Configurable rules
458459

RULES_DESCRIPTIONS.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ List of all available rules.
6767
- [unreachable-code](#unreachable-code)
6868
- [unused-parameter](#unused-parameter)
6969
- [unused-receiver](#unused-receiver)
70+
- [useless-break](#useless-break)
7071
- [waitgroup-by-value](#waitgroup-by-value)
7172

7273
## add-constant
@@ -612,6 +613,16 @@ _Description_: This rule warns on unused method receivers. Methods with unused r
612613

613614
_Configuration_: N/A
614615

616+
## useless-break
617+
618+
_Description_: This rule warns on useless `break` statements in case clauses of switch and select statements. GO, unlike other programming languages like C, only executes statements of the selected case while ignoring the subsequent case clauses.
619+
Therefore, inserting a `break` at the end of a case clause has no effect.
620+
621+
Because `break` statements are rarely used in case clauses, when switch or select statements are inside a for-loop, the programmer might wrongly assume that a `break` in a case clause will take the control out of the loop.
622+
The rule emits a specific warning for such cases.
623+
624+
_Configuration_: N/A
625+
615626
## waitgroup-by-value
616627

617628
_Description_: Function parameters that are passed by value, are in fact a copy of the original argument. Passing a copy of a `sync.WaitGroup` is usually not what the developer wants to do.

config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ var allRules = append([]lint.Rule{
8080
&rule.FunctionLength{},
8181
&rule.NestedStructs{},
8282
&rule.IfReturnRule{},
83+
&rule.UselessBreak{},
8384
}, defaultRules...)
8485

8586
var allFormatters = []lint.Formatter{

rule/useless-break.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package rule
2+
3+
import (
4+
"go/ast"
5+
"go/token"
6+
7+
"github.com/mgechev/revive/lint"
8+
)
9+
10+
// UselessBreak lint rule.
11+
type UselessBreak struct{}
12+
13+
// Apply applies the rule to given file.
14+
func (r *UselessBreak) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
15+
var failures []lint.Failure
16+
17+
onFailure := func(failure lint.Failure) {
18+
failures = append(failures, failure)
19+
}
20+
21+
astFile := file.AST
22+
w := &lintUselessBreak{onFailure, false}
23+
ast.Walk(w, astFile)
24+
return failures
25+
}
26+
27+
// Name returns the rule name.
28+
func (r *UselessBreak) Name() string {
29+
return "useless-break"
30+
}
31+
32+
type lintUselessBreak struct {
33+
onFailure func(lint.Failure)
34+
inLoopBody bool
35+
}
36+
37+
func (w *lintUselessBreak) Visit(node ast.Node) ast.Visitor {
38+
switch v := node.(type) {
39+
case *ast.ForStmt:
40+
w.inLoopBody = true
41+
ast.Walk(w, v.Body)
42+
w.inLoopBody = false
43+
return nil
44+
case *ast.CommClause:
45+
for _, n := range v.Body {
46+
w.inspectCaseStatement(n)
47+
}
48+
return nil
49+
case *ast.CaseClause:
50+
for _, n := range v.Body {
51+
w.inspectCaseStatement(n)
52+
}
53+
return nil
54+
}
55+
return w
56+
}
57+
58+
func (w *lintUselessBreak) inspectCaseStatement(n ast.Stmt) {
59+
switch s := n.(type) {
60+
case *ast.BranchStmt:
61+
if s.Tok != token.BREAK {
62+
return // not a break statement
63+
}
64+
if s.Label != nil {
65+
return // labeled break statement, usually affects a nesting loop
66+
}
67+
msg := "useless break in case clause"
68+
if w.inLoopBody {
69+
msg += " (WARN: this break statement affects this switch or select statement and not the loop enclosing it)"
70+
}
71+
w.onFailure(lint.Failure{
72+
Confidence: 1,
73+
Node: s,
74+
Failure: msg,
75+
})
76+
}
77+
}

test/useless-break_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/mgechev/revive/rule"
7+
)
8+
9+
// UselessBreak rule.
10+
func TestUselessBreak(t *testing.T) {
11+
testRule(t, "useless-break", &rule.UselessBreak{})
12+
}

testdata/useless-break.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package fixtures
2+
3+
import "reflect"
4+
5+
func UselessBreaks() {
6+
7+
switch {
8+
case true:
9+
break // MATCH /useless break in case clause/
10+
case false:
11+
if false {
12+
break
13+
}
14+
}
15+
16+
select {
17+
case c:
18+
break // MATCH /useless break in case clause/
19+
case n:
20+
if true {
21+
if false {
22+
break
23+
}
24+
break
25+
}
26+
}
27+
28+
for {
29+
switch {
30+
case c1:
31+
break // MATCH /useless break in case clause (WARN: this break statement affects the switch or select statement and not the loop enclosing it)/
32+
}
33+
}
34+
35+
for _, node := range desc.Args {
36+
switch node := node.(type) {
37+
case *ast.FuncLit:
38+
found = true
39+
funcLit = node
40+
break // MATCH /useless break in case clause (WARN: this break statement affects the switch or select statement and not the loop enclosing it)/
41+
}
42+
}
43+
44+
switch val.Kind() {
45+
case reflect.Array, reflect.Slice:
46+
if val.Len() == 0 {
47+
break
48+
}
49+
for i := 0; i < val.Len(); i++ {
50+
oneIteration(reflect.ValueOf(i), val.Index(i))
51+
}
52+
return
53+
case reflect.Map:
54+
if val.Len() == 0 {
55+
break
56+
}
57+
om := fmtsort.Sort(val)
58+
for i, key := range om.Key {
59+
oneIteration(key, om.Value[i])
60+
}
61+
return
62+
case reflect.Chan:
63+
if val.IsNil() {
64+
break
65+
}
66+
if val.Type().ChanDir() == reflect.SendDir {
67+
s.errorf("range over send-only channel %v", val)
68+
break
69+
}
70+
i := 0
71+
for ; ; i++ {
72+
elem, ok := val.Recv()
73+
if !ok {
74+
break
75+
}
76+
oneIteration(reflect.ValueOf(i), elem)
77+
}
78+
if i == 0 {
79+
break
80+
}
81+
return
82+
case reflect.Invalid:
83+
break // MATCH /useless break in case clause/
84+
default:
85+
s.errorf("range can't iterate over %v", val)
86+
}
87+
}

0 commit comments

Comments
 (0)