Skip to content

Commit dc28699

Browse files
committed
feat(linters): add new linter gobreakselectinfor
1 parent adbdfdb commit dc28699

File tree

6 files changed

+125
-0
lines changed

6 files changed

+125
-0
lines changed

.golangci.next.reference.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2620,6 +2620,7 @@ linters:
26202620
- funlen
26212621
- gci
26222622
- ginkgolinter
2623+
- gobreakselectinfor
26232624
- gocheckcompilerdirectives
26242625
- gochecknoglobals
26252626
- gochecknoinits
@@ -2735,6 +2736,7 @@ linters:
27352736
- funlen
27362737
- gci
27372738
- ginkgolinter
2739+
- gobreakselectinfor
27382740
- gocheckcompilerdirectives
27392741
- gochecknoglobals
27402742
- gochecknoinits

jsonschema/golangci.next.jsonschema.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@
324324
"funlen",
325325
"gci",
326326
"ginkgolinter",
327+
"gobreakselectinfor",
327328
"gocheckcompilerdirectives",
328329
"gochecknoglobals",
329330
"gochecknoinits",
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package gobreakselectinfor
2+
3+
import (
4+
"go/ast"
5+
"go/token"
6+
"sync"
7+
8+
"golang.org/x/tools/go/analysis"
9+
10+
"github.com/golangci/golangci-lint/pkg/goanalysis"
11+
"github.com/golangci/golangci-lint/pkg/lint/linter"
12+
"github.com/golangci/golangci-lint/pkg/result"
13+
)
14+
15+
const linterName = "gobreakselectinfor"
16+
17+
func New() *goanalysis.Linter {
18+
var mu sync.Mutex
19+
var resIssues []goanalysis.Issue
20+
21+
analyzer := &analysis.Analyzer{
22+
Name: linterName,
23+
Doc: goanalysis.TheOnlyanalyzerDoc,
24+
Run: func(pass *analysis.Pass) (any, error) {
25+
fileIssues := run(pass)
26+
res := make([]goanalysis.Issue, 0, len(fileIssues))
27+
for i := range fileIssues {
28+
res = append(res, goanalysis.NewIssue(&fileIssues[i], pass))
29+
}
30+
if len(res) == 0 {
31+
return nil, nil
32+
}
33+
34+
mu.Lock()
35+
resIssues = append(resIssues, res...)
36+
mu.Unlock()
37+
38+
return nil, nil
39+
},
40+
}
41+
42+
return goanalysis.NewLinter(
43+
linterName,
44+
"Checks that break statement inside select statement inside for loop",
45+
[]*analysis.Analyzer{analyzer},
46+
nil,
47+
).WithIssuesReporter(func(*linter.Context) []goanalysis.Issue {
48+
return resIssues
49+
}).WithLoadMode(goanalysis.LoadModeSyntax)
50+
}
51+
52+
func run(pass *analysis.Pass) []result.Issue {
53+
var res []result.Issue
54+
55+
inspect := func(node ast.Node) bool {
56+
funcDecl, ok := node.(*ast.FuncDecl)
57+
if !ok {
58+
return true
59+
}
60+
61+
ast.Inspect(funcDecl.Body, func(stmt ast.Node) bool {
62+
if forStmt, ok := stmt.(*ast.ForStmt); ok {
63+
ast.Inspect(forStmt.Body, func(stmt ast.Node) bool {
64+
if selStmt, ok := stmt.(*ast.SelectStmt); ok {
65+
ast.Inspect(selStmt.Body, func(stmt ast.Node) bool {
66+
if brkStmt, ok := stmt.(*ast.BranchStmt); ok && brkStmt.Tok == token.BREAK {
67+
pass.Reportf(stmt.Pos(), "break statement inside select statement inside for loop")
68+
res = append(res, result.Issue{
69+
Pos: pass.Fset.Position(stmt.Pos()),
70+
Text: "break statement inside select statement inside for loop",
71+
FromLinter: linterName,
72+
})
73+
return true
74+
}
75+
return true
76+
})
77+
}
78+
return true
79+
})
80+
}
81+
return true
82+
})
83+
84+
return true
85+
}
86+
87+
for _, f := range pass.Files {
88+
ast.Inspect(f, inspect)
89+
}
90+
91+
return res
92+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package gobreakselectinfor
2+
3+
import (
4+
"testing"
5+
6+
"github.com/golangci/golangci-lint/test/testshared/integration"
7+
)
8+
9+
func TestFromTestdata(t *testing.T) {
10+
integration.RunTestdata(t)
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//golangcitest:args -Egobreakselectinfor
2+
package testdata
3+
4+
func bad() {
5+
var ch chan string
6+
for {
7+
select {
8+
case <-ch:
9+
break // want "break statement inside select statement inside for loop"
10+
}
11+
}
12+
}

pkg/lint/lintersdb/builder_linter.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/golangci/golangci-lint/pkg/golinters/funlen"
3434
"github.com/golangci/golangci-lint/pkg/golinters/gci"
3535
"github.com/golangci/golangci-lint/pkg/golinters/ginkgolinter"
36+
"github.com/golangci/golangci-lint/pkg/golinters/gobreakselectinfor"
3637
"github.com/golangci/golangci-lint/pkg/golinters/gocheckcompilerdirectives"
3738
"github.com/golangci/golangci-lint/pkg/golinters/gochecknoglobals"
3839
"github.com/golangci/golangci-lint/pkg/golinters/gochecknoinits"
@@ -318,6 +319,12 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) {
318319
WithPresets(linter.PresetStyle).
319320
WithURL("https://github.com/nunnatsa/ginkgolinter"),
320321

322+
linter.NewConfig(gobreakselectinfor.New()).
323+
WithSince("1.61.0").
324+
WithPresets(linter.PresetStyle).
325+
WithLoadForGoAnalysis().
326+
WithURL("https://github.com/rnben/go-break-select-in-for"),
327+
321328
linter.NewConfig(gocheckcompilerdirectives.New()).
322329
WithSince("v1.51.0").
323330
WithPresets(linter.PresetBugs).

0 commit comments

Comments
 (0)