Skip to content

Commit ad91b59

Browse files
committed
feat: add gochecksumtype linter
`gochecksumtype` is a linter that checks that type switches on "sum types" in Go are exhaustive. https://github.com/alecthomas/go-check-sumtype This is based on BurntSushi/go-sumtype, but fixes, modernises and simplifies it.
1 parent 28a94fd commit ad91b59

File tree

7 files changed

+119
-1
lines changed

7 files changed

+119
-1
lines changed

.golangci.reference.yml

+2
Original file line numberDiff line numberDiff line change
@@ -2027,6 +2027,7 @@ linters:
20272027
- gocheckcompilerdirectives
20282028
- gochecknoglobals
20292029
- gochecknoinits
2030+
- gochecksumtype
20302031
- gocognit
20312032
- goconst
20322033
- gocritic
@@ -2137,6 +2138,7 @@ linters:
21372138
- gocheckcompilerdirectives
21382139
- gochecknoglobals
21392140
- gochecknoinits
2141+
- gochecksumtype
21402142
- gocognit
21412143
- goconst
21422144
- gocritic

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ require (
119119

120120
require (
121121
github.com/Masterminds/semver v1.5.0 // indirect
122+
github.com/alecthomas/go-check-sumtype v0.1.3
122123
github.com/beorn7/perks v1.0.1 // indirect
123124
github.com/cespare/xxhash/v2 v2.1.2 // indirect
124125
github.com/chavacava/garif v0.0.0-20221024190013-b3ef35877348 // indirect

go.sum

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/golinters/gochecksumtype.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package golinters
2+
3+
import (
4+
"strings"
5+
"sync"
6+
7+
gochecksumtype "github.com/alecthomas/go-check-sumtype"
8+
"golang.org/x/tools/go/analysis"
9+
"golang.org/x/tools/go/packages"
10+
11+
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
12+
"github.com/golangci/golangci-lint/pkg/lint/linter"
13+
"github.com/golangci/golangci-lint/pkg/result"
14+
)
15+
16+
const goCheckSumTypeName = "gochecksumtype"
17+
18+
func NewGoCheckSumType() *goanalysis.Linter {
19+
var mu sync.Mutex
20+
var resIssues []goanalysis.Issue
21+
22+
analyzer := &analysis.Analyzer{
23+
Name: goCheckSumTypeName,
24+
Doc: goanalysis.TheOnlyanalyzerDoc,
25+
Run: func(pass *analysis.Pass) (interface{}, error) {
26+
issues, err := runGoCheckSumType(pass)
27+
if err != nil {
28+
return nil, err
29+
}
30+
if len(issues) == 0 {
31+
return nil, nil
32+
}
33+
mu.Lock()
34+
resIssues = append(resIssues, issues...)
35+
mu.Unlock()
36+
return nil, nil
37+
},
38+
}
39+
return goanalysis.NewLinter(
40+
goCheckSumTypeName,
41+
`Run exhaustiveness checks on Go "sum types"`,
42+
[]*analysis.Analyzer{analyzer},
43+
nil,
44+
).WithIssuesReporter(func(ctx *linter.Context) []goanalysis.Issue {
45+
return resIssues
46+
}).WithLoadMode(goanalysis.LoadModeTypesInfo)
47+
}
48+
49+
func runGoCheckSumType(pass *analysis.Pass) ([]goanalysis.Issue, error) {
50+
resIssues := []goanalysis.Issue{}
51+
pkg := &packages.Package{
52+
Fset: pass.Fset,
53+
Syntax: pass.Files,
54+
Types: pass.Pkg,
55+
TypesInfo: pass.TypesInfo,
56+
}
57+
var unknownError error
58+
errors := gochecksumtype.Run([]*packages.Package{pkg})
59+
for _, err := range errors {
60+
err, ok := err.(gochecksumtype.Error)
61+
if !ok {
62+
unknownError = err
63+
continue
64+
}
65+
prefix := err.Pos().String() + ": "
66+
resIssues = append(resIssues, goanalysis.NewIssue(&result.Issue{
67+
FromLinter: goCheckSumTypeName,
68+
Text: strings.TrimPrefix(err.Error(), prefix),
69+
Pos: err.Pos(),
70+
}, pass))
71+
}
72+
return resIssues, unknownError
73+
}

pkg/golinters/unparam.go

-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ func NewUnparam(settings *config.UnparamSettings) *goanalysis.Linter {
3737
mu.Lock()
3838
resIssues = append(resIssues, issues...)
3939
mu.Unlock()
40-
4140
return nil, nil
4241
},
4342
}

pkg/lint/lintersdb/manager.go

+5
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,11 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
456456
WithSince("v1.12.0").
457457
WithPresets(linter.PresetStyle),
458458

459+
linter.NewConfig(golinters.NewGoCheckSumType()).
460+
WithSince("v1.51.2").
461+
WithPresets(linter.PresetBugs).
462+
WithURL("https://github.com/alecthomas/go-check-sumtype"),
463+
459464
linter.NewConfig(golinters.NewGocognit(gocognitCfg)).
460465
WithSince("v1.20.0").
461466
WithPresets(linter.PresetComplexity).

test/testdata/gochecksumtype.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//golangcitest:args -Egochecksumtype
2+
package testdata
3+
4+
//sumtype:decl
5+
type SumType interface{ isSumType() }
6+
7+
//sumtype:decl
8+
type One struct{} // want "type 'One' is not an interface"
9+
10+
func (One) isSumType() {}
11+
12+
type Two struct{}
13+
14+
func (Two) isSumType() {}
15+
16+
func sumTypeTest() {
17+
var sum SumType = One{}
18+
switch sum.(type) { // want "exhaustiveness check failed for sum type.*SumType.*missing cases for Two"
19+
case One:
20+
}
21+
22+
switch sum.(type) { // want "exhaustiveness check failed for sum type.*SumType.*missing cases for Two"
23+
case One:
24+
default:
25+
panic("??")
26+
}
27+
28+
switch sum.(type) {
29+
case One:
30+
case Two:
31+
}
32+
}

0 commit comments

Comments
 (0)