From 97ce207901fc35ca9402118a50793f2f9c16b0f9 Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Sun, 12 Mar 2023 20:33:42 +1100 Subject: [PATCH 1/6] 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. --- .golangci.reference.yml | 2 + go.mod | 1 + go.sum | 4 ++ pkg/golinters/gochecksumtype.go | 73 +++++++++++++++++++++++++++++++++ pkg/golinters/unparam.go | 1 - pkg/lint/lintersdb/manager.go | 5 +++ test/testdata/gochecksumtype.go | 32 +++++++++++++++ 7 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 pkg/golinters/gochecksumtype.go create mode 100644 test/testdata/gochecksumtype.go diff --git a/.golangci.reference.yml b/.golangci.reference.yml index 41200df5b3e0..7cb3f6cbc525 100644 --- a/.golangci.reference.yml +++ b/.golangci.reference.yml @@ -2214,6 +2214,7 @@ linters: - gocheckcompilerdirectives - gochecknoglobals - gochecknoinits + - gochecksumtype - gocognit - goconst - gocritic @@ -2329,6 +2330,7 @@ linters: - gocheckcompilerdirectives - gochecknoglobals - gochecknoinits + - gochecksumtype - gocognit - goconst - gocritic diff --git a/go.mod b/go.mod index 61b1d8beec39..e1f68eb9aac2 100644 --- a/go.mod +++ b/go.mod @@ -128,6 +128,7 @@ require ( require ( github.com/Masterminds/semver v1.5.0 // indirect + github.com/alecthomas/go-check-sumtype v0.1.3 github.com/beorn7/perks v1.0.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect diff --git a/go.sum b/go.sum index f3ecc2a54204..dae4eab94997 100644 --- a/go.sum +++ b/go.sum @@ -62,6 +62,10 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/OpenPeeDeeP/depguard/v2 v2.1.0 h1:aQl70G173h/GZYhWf36aE5H0KaujXfVMnn/f1kSDVYY= github.com/OpenPeeDeeP/depguard/v2 v2.1.0/go.mod h1:PUBgk35fX4i7JDmwzlJwJ+GMe6NfO1723wmJMgPThNQ= +github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= +github.com/alecthomas/go-check-sumtype v0.1.3 h1:M+tqMxB68hcgccRXBMVCPI4UJ+QUfdSx0xdbypKCqA8= +github.com/alecthomas/go-check-sumtype v0.1.3/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= +github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= diff --git a/pkg/golinters/gochecksumtype.go b/pkg/golinters/gochecksumtype.go new file mode 100644 index 000000000000..b8ff5b662418 --- /dev/null +++ b/pkg/golinters/gochecksumtype.go @@ -0,0 +1,73 @@ +package golinters + +import ( + "strings" + "sync" + + gochecksumtype "github.com/alecthomas/go-check-sumtype" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/packages" + + "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" + "github.com/golangci/golangci-lint/pkg/lint/linter" + "github.com/golangci/golangci-lint/pkg/result" +) + +const goCheckSumTypeName = "gochecksumtype" + +func NewGoCheckSumType() *goanalysis.Linter { + var mu sync.Mutex + var resIssues []goanalysis.Issue + + analyzer := &analysis.Analyzer{ + Name: goCheckSumTypeName, + Doc: goanalysis.TheOnlyanalyzerDoc, + Run: func(pass *analysis.Pass) (interface{}, error) { + issues, err := runGoCheckSumType(pass) + if err != nil { + return nil, err + } + if len(issues) == 0 { + return nil, nil + } + mu.Lock() + resIssues = append(resIssues, issues...) + mu.Unlock() + return nil, nil + }, + } + return goanalysis.NewLinter( + goCheckSumTypeName, + `Run exhaustiveness checks on Go "sum types"`, + []*analysis.Analyzer{analyzer}, + nil, + ).WithIssuesReporter(func(ctx *linter.Context) []goanalysis.Issue { + return resIssues + }).WithLoadMode(goanalysis.LoadModeTypesInfo) +} + +func runGoCheckSumType(pass *analysis.Pass) ([]goanalysis.Issue, error) { + resIssues := []goanalysis.Issue{} + pkg := &packages.Package{ + Fset: pass.Fset, + Syntax: pass.Files, + Types: pass.Pkg, + TypesInfo: pass.TypesInfo, + } + var unknownError error + errors := gochecksumtype.Run([]*packages.Package{pkg}) + for _, err := range errors { + err, ok := err.(gochecksumtype.Error) + if !ok { + unknownError = err + continue + } + prefix := err.Pos().String() + ": " + resIssues = append(resIssues, goanalysis.NewIssue(&result.Issue{ + FromLinter: goCheckSumTypeName, + Text: strings.TrimPrefix(err.Error(), prefix), + Pos: err.Pos(), + }, pass)) + } + return resIssues, unknownError +} diff --git a/pkg/golinters/unparam.go b/pkg/golinters/unparam.go index 4078d94988fb..26f74004d9bb 100644 --- a/pkg/golinters/unparam.go +++ b/pkg/golinters/unparam.go @@ -37,7 +37,6 @@ func NewUnparam(settings *config.UnparamSettings) *goanalysis.Linter { mu.Lock() resIssues = append(resIssues, issues...) mu.Unlock() - return nil, nil }, } diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 59f6ef30c0c2..8d191d9da8af 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -441,6 +441,11 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithSince("v1.12.0"). WithPresets(linter.PresetStyle), + linter.NewConfig(golinters.NewGoCheckSumType()). + WithSince("v1.51.2"). + WithPresets(linter.PresetBugs). + WithURL("https://github.com/alecthomas/go-check-sumtype"), + linter.NewConfig(golinters.NewGocognit(gocognitCfg)). WithSince("v1.20.0"). WithPresets(linter.PresetComplexity). diff --git a/test/testdata/gochecksumtype.go b/test/testdata/gochecksumtype.go new file mode 100644 index 000000000000..1783eeb0fc0f --- /dev/null +++ b/test/testdata/gochecksumtype.go @@ -0,0 +1,32 @@ +//golangcitest:args -Egochecksumtype +package testdata + +//sumtype:decl +type SumType interface{ isSumType() } + +//sumtype:decl +type One struct{} // want "type 'One' is not an interface" + +func (One) isSumType() {} + +type Two struct{} + +func (Two) isSumType() {} + +func sumTypeTest() { + var sum SumType = One{} + switch sum.(type) { // want "exhaustiveness check failed for sum type.*SumType.*missing cases for Two" + case One: + } + + switch sum.(type) { // want "exhaustiveness check failed for sum type.*SumType.*missing cases for Two" + case One: + default: + panic("??") + } + + switch sum.(type) { + case One: + case Two: + } +} From 42e9e3ec5a9e2184400fcacea8358795837a58f3 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 16 Apr 2023 18:06:12 +0200 Subject: [PATCH 2/6] review --- go.mod | 2 +- pkg/golinters/gochecksumtype.go | 13 ++++++++++--- pkg/golinters/unparam.go | 1 + pkg/lint/lintersdb/manager.go | 3 ++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index e1f68eb9aac2..32fcc2aa5f43 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0 github.com/OpenPeeDeeP/depguard/v2 v2.1.0 + github.com/alecthomas/go-check-sumtype v0.1.3 github.com/alexkohler/nakedret/v2 v2.0.2 github.com/alexkohler/prealloc v1.0.0 github.com/alingse/asasalint v0.0.11 @@ -128,7 +129,6 @@ require ( require ( github.com/Masterminds/semver v1.5.0 // indirect - github.com/alecthomas/go-check-sumtype v0.1.3 github.com/beorn7/perks v1.0.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect diff --git a/pkg/golinters/gochecksumtype.go b/pkg/golinters/gochecksumtype.go index b8ff5b662418..4f7715855c28 100644 --- a/pkg/golinters/gochecksumtype.go +++ b/pkg/golinters/gochecksumtype.go @@ -27,15 +27,19 @@ func NewGoCheckSumType() *goanalysis.Linter { if err != nil { return nil, err } + if len(issues) == 0 { return nil, nil } + mu.Lock() resIssues = append(resIssues, issues...) mu.Unlock() + return nil, nil }, } + return goanalysis.NewLinter( goCheckSumTypeName, `Run exhaustiveness checks on Go "sum types"`, @@ -47,13 +51,15 @@ func NewGoCheckSumType() *goanalysis.Linter { } func runGoCheckSumType(pass *analysis.Pass) ([]goanalysis.Issue, error) { - resIssues := []goanalysis.Issue{} + var resIssues []goanalysis.Issue + pkg := &packages.Package{ Fset: pass.Fset, Syntax: pass.Files, Types: pass.Pkg, TypesInfo: pass.TypesInfo, } + var unknownError error errors := gochecksumtype.Run([]*packages.Package{pkg}) for _, err := range errors { @@ -62,12 +68,13 @@ func runGoCheckSumType(pass *analysis.Pass) ([]goanalysis.Issue, error) { unknownError = err continue } - prefix := err.Pos().String() + ": " + resIssues = append(resIssues, goanalysis.NewIssue(&result.Issue{ FromLinter: goCheckSumTypeName, - Text: strings.TrimPrefix(err.Error(), prefix), + Text: strings.TrimPrefix(err.Error(), err.Pos().String()+": "), Pos: err.Pos(), }, pass)) } + return resIssues, unknownError } diff --git a/pkg/golinters/unparam.go b/pkg/golinters/unparam.go index 26f74004d9bb..4078d94988fb 100644 --- a/pkg/golinters/unparam.go +++ b/pkg/golinters/unparam.go @@ -37,6 +37,7 @@ func NewUnparam(settings *config.UnparamSettings) *goanalysis.Linter { mu.Lock() resIssues = append(resIssues, issues...) mu.Unlock() + return nil, nil }, } diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 8d191d9da8af..220ac164aca3 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -442,8 +442,9 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle), linter.NewConfig(golinters.NewGoCheckSumType()). - WithSince("v1.51.2"). + WithSince("v1.53.0"). WithPresets(linter.PresetBugs). + WithLoadForGoAnalysis(). WithURL("https://github.com/alecthomas/go-check-sumtype"), linter.NewConfig(golinters.NewGocognit(gocognitCfg)). From 627d68e8e8c59ac678ba20b0c476558dd3527eee Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 16 Apr 2023 18:07:08 +0200 Subject: [PATCH 3/6] review: add one std lib import --- test/testdata/gochecksumtype.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/testdata/gochecksumtype.go b/test/testdata/gochecksumtype.go index 1783eeb0fc0f..a3635b2b9ade 100644 --- a/test/testdata/gochecksumtype.go +++ b/test/testdata/gochecksumtype.go @@ -1,6 +1,10 @@ //golangcitest:args -Egochecksumtype package testdata +import ( + "log" +) + //sumtype:decl type SumType interface{ isSumType() } @@ -22,6 +26,7 @@ func sumTypeTest() { switch sum.(type) { // want "exhaustiveness check failed for sum type.*SumType.*missing cases for Two" case One: default: + log.Println("??") panic("??") } From 13ad6452bfc438ea51e40f0f37c7afcf59fc8ecb Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 16 Apr 2023 18:10:55 +0200 Subject: [PATCH 4/6] review --- pkg/golinters/gochecksumtype.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/golinters/gochecksumtype.go b/pkg/golinters/gochecksumtype.go index 4f7715855c28..fcc0cad5889e 100644 --- a/pkg/golinters/gochecksumtype.go +++ b/pkg/golinters/gochecksumtype.go @@ -22,7 +22,7 @@ func NewGoCheckSumType() *goanalysis.Linter { analyzer := &analysis.Analyzer{ Name: goCheckSumTypeName, Doc: goanalysis.TheOnlyanalyzerDoc, - Run: func(pass *analysis.Pass) (interface{}, error) { + Run: func(pass *analysis.Pass) (any, error) { issues, err := runGoCheckSumType(pass) if err != nil { return nil, err From dff03203ff231211523ce6d18117e1de61933cec Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Sun, 16 Apr 2023 18:14:58 +0200 Subject: [PATCH 5/6] review --- test/testdata/gochecksumtype.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/testdata/gochecksumtype.go b/test/testdata/gochecksumtype.go index a3635b2b9ade..47ae3b9a90d1 100644 --- a/test/testdata/gochecksumtype.go +++ b/test/testdata/gochecksumtype.go @@ -26,10 +26,11 @@ func sumTypeTest() { switch sum.(type) { // want "exhaustiveness check failed for sum type.*SumType.*missing cases for Two" case One: default: - log.Println("??") panic("??") } + log.Println("??") + switch sum.(type) { case One: case Two: From 90c35d69f40962a1f3a971ffcee06770609c26c7 Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Mon, 9 Oct 2023 17:18:56 +0200 Subject: [PATCH 6/6] review: update Since attribute --- pkg/lint/lintersdb/manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/lint/lintersdb/manager.go b/pkg/lint/lintersdb/manager.go index 220ac164aca3..a76984ebf812 100644 --- a/pkg/lint/lintersdb/manager.go +++ b/pkg/lint/lintersdb/manager.go @@ -442,7 +442,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config { WithPresets(linter.PresetStyle), linter.NewConfig(golinters.NewGoCheckSumType()). - WithSince("v1.53.0"). + WithSince("v1.55.0"). WithPresets(linter.PresetBugs). WithLoadForGoAnalysis(). WithURL("https://github.com/alecthomas/go-check-sumtype"),