feat: add exhaustruct
linter (#2667)
Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
This commit is contained in:
parent
acceecf661
commit
380699a099
@ -285,6 +285,16 @@ linters-settings:
|
||||
- '*.Test'
|
||||
- 'example.com/package.ExampleStruct'
|
||||
|
||||
exhaustruct:
|
||||
# List of regular expressions to match struct packages and names.
|
||||
# If this list is empty, all structs are tested.
|
||||
include:
|
||||
- '.*\.Test'
|
||||
- 'example\.com/package\.ExampleStruct[\d]{1,2}'
|
||||
# List of regular expressions to exclude struct packages and names from check.
|
||||
exclude:
|
||||
- 'cobra\.Command$'
|
||||
|
||||
forbidigo:
|
||||
# Forbid the following identifiers (list of regexp).
|
||||
forbid:
|
||||
|
1
go.mod
1
go.mod
@ -8,6 +8,7 @@ require (
|
||||
github.com/Antonboom/nilnil v0.1.1
|
||||
github.com/BurntSushi/toml v1.1.0
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v2 v2.1.0
|
||||
github.com/OpenPeeDeeP/depguard v1.1.0
|
||||
github.com/alexkohler/prealloc v1.0.0
|
||||
github.com/ashanbrown/forbidigo v1.3.0
|
||||
|
2
go.sum
generated
2
go.sum
generated
@ -62,6 +62,8 @@ github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM=
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v2 v2.1.0 h1:LAPPhJ4KR5Z8aKVZF5S48csJkxL5RMKmE/98fMs1u5M=
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v2 v2.1.0/go.mod h1:LGOGuvEgCfCQsy3JF2tRmpGDpzA53iZfyGEWSPwQ6/4=
|
||||
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
|
@ -123,6 +123,7 @@ type LintersSettings struct {
|
||||
ErrorLint ErrorLintSettings
|
||||
Exhaustive ExhaustiveSettings
|
||||
ExhaustiveStruct ExhaustiveStructSettings
|
||||
Exhaustruct ExhaustructSettings
|
||||
Forbidigo ForbidigoSettings
|
||||
Funlen FunlenSettings
|
||||
Gci GciSettings
|
||||
@ -255,6 +256,11 @@ type ExhaustiveStructSettings struct {
|
||||
StructPatterns []string `mapstructure:"struct-patterns"`
|
||||
}
|
||||
|
||||
type ExhaustructSettings struct {
|
||||
Include []string `mapstructure:"include"`
|
||||
Exclude []string `mapstructure:"exclude"`
|
||||
}
|
||||
|
||||
type ForbidigoSettings struct {
|
||||
Forbid []string `mapstructure:"forbid"`
|
||||
ExcludeGodocExamples bool `mapstructure:"exclude-godoc-examples"`
|
||||
|
6
pkg/golinters/commons.go
Normal file
6
pkg/golinters/commons.go
Normal file
@ -0,0 +1,6 @@
|
||||
package golinters
|
||||
|
||||
import "github.com/golangci/golangci-lint/pkg/logutils"
|
||||
|
||||
// linterLogger must be use only when the context logger is not available.
|
||||
var linterLogger = logutils.NewStderrLog("linter")
|
26
pkg/golinters/exhaustruct.go
Normal file
26
pkg/golinters/exhaustruct.go
Normal file
@ -0,0 +1,26 @@
|
||||
package golinters
|
||||
|
||||
import (
|
||||
"github.com/GaijinEntertainment/go-exhaustruct/v2/pkg/analyzer"
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
|
||||
)
|
||||
|
||||
func NewExhaustruct(settings *config.ExhaustructSettings) *goanalysis.Linter {
|
||||
var include, exclude []string
|
||||
|
||||
if settings != nil {
|
||||
include = settings.Include
|
||||
exclude = settings.Exclude
|
||||
}
|
||||
|
||||
a, err := analyzer.NewAnalyzer(include, exclude)
|
||||
if err != nil {
|
||||
linterLogger.Fatalf("exhaustruct configuration: %v", err)
|
||||
}
|
||||
|
||||
return goanalysis.NewLinter(a.Name, a.Doc, []*analysis.Analyzer{a}, nil).
|
||||
WithLoadMode(goanalysis.LoadModeTypesInfo)
|
||||
}
|
@ -107,6 +107,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
|
||||
var errorlintCfg *config.ErrorLintSettings
|
||||
var exhaustiveCfg *config.ExhaustiveSettings
|
||||
var exhaustiveStructCfg *config.ExhaustiveStructSettings
|
||||
var exhaustructCfg *config.ExhaustructSettings
|
||||
var gciCfg *config.GciSettings
|
||||
var goModDirectivesCfg *config.GoModDirectivesSettings
|
||||
var goMndCfg *config.GoMndSettings
|
||||
@ -140,6 +141,7 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
|
||||
errorlintCfg = &m.cfg.LintersSettings.ErrorLint
|
||||
exhaustiveCfg = &m.cfg.LintersSettings.Exhaustive
|
||||
exhaustiveStructCfg = &m.cfg.LintersSettings.ExhaustiveStruct
|
||||
exhaustructCfg = &m.cfg.LintersSettings.Exhaustruct
|
||||
gciCfg = &m.cfg.LintersSettings.Gci
|
||||
goModDirectivesCfg = &m.cfg.LintersSettings.GoModDirectives
|
||||
goMndCfg = &m.cfg.LintersSettings.Gomnd
|
||||
@ -281,7 +283,14 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
|
||||
WithSince("v1.32.0").
|
||||
WithPresets(linter.PresetStyle, linter.PresetTest).
|
||||
WithLoadForGoAnalysis().
|
||||
WithURL("https://github.com/mbilski/exhaustivestruct"),
|
||||
WithURL("https://github.com/mbilski/exhaustivestruct").
|
||||
Deprecated("The owner seems to have abandoned the linter.", "v1.46.0", "exhaustruct"),
|
||||
|
||||
linter.NewConfig(golinters.NewExhaustruct(exhaustructCfg)).
|
||||
WithSince("v1.46.0").
|
||||
WithPresets(linter.PresetStyle, linter.PresetTest).
|
||||
WithLoadForGoAnalysis().
|
||||
WithURL("https://github.com/GaijinEntertainment/go-exhaustruct"),
|
||||
|
||||
linter.NewConfig(golinters.NewExportLoopRef()).
|
||||
WithSince("v1.28.0").
|
||||
|
5
test/testdata/configs/exhaustivestruct.yml
vendored
Normal file
5
test/testdata/configs/exhaustivestruct.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
linters-settings:
|
||||
exhaustivestruct:
|
||||
struct-patterns:
|
||||
- '*.ExhaustiveStructCustom'
|
||||
- '*.ExhaustiveStructCustom2'
|
6
test/testdata/configs/exhaustruct.yml
vendored
Normal file
6
test/testdata/configs/exhaustruct.yml
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
linters-settings:
|
||||
exhaustruct:
|
||||
include:
|
||||
- .*\.ExhaustructCustom
|
||||
exclude:
|
||||
- .*\.ExhaustructCustom[\d]{1,2}
|
56
test/testdata/exhaustivestruct.go
vendored
56
test/testdata/exhaustivestruct.go
vendored
@ -1,9 +1,9 @@
|
||||
//args: -Eexhaustivestruct
|
||||
// args: -Eexhaustivestruct --internal-cmd-test
|
||||
package testdata
|
||||
|
||||
import "time"
|
||||
|
||||
type Test struct {
|
||||
type ExhaustiveStruct struct {
|
||||
A string
|
||||
B int
|
||||
c bool // private field inside the same package are not ignored
|
||||
@ -11,30 +11,36 @@ type Test struct {
|
||||
E time.Time
|
||||
}
|
||||
|
||||
var pass = Test{
|
||||
A: "a",
|
||||
B: 0,
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
func exhaustiveStruct() {
|
||||
// pass
|
||||
_ = ExhaustiveStruct{
|
||||
A: "a",
|
||||
B: 0,
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
var failPrivate = Test{ // ERROR "c is missing in Test"
|
||||
A: "a",
|
||||
B: 0,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
// failPrivate
|
||||
_ = ExhaustiveStruct{ // ERROR "c is missing in ExhaustiveStruct"
|
||||
A: "a",
|
||||
B: 0,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
var fail = Test{ // ERROR "B is missing in Test"
|
||||
A: "a",
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
// fail
|
||||
_ = ExhaustiveStruct{ // ERROR "B is missing in ExhaustiveStruct"
|
||||
A: "a",
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
var failMultiple = Test{ // ERROR "B, D are missing in Test"
|
||||
A: "a",
|
||||
c: false,
|
||||
E: time.Now(),
|
||||
// failMultiple
|
||||
_ = ExhaustiveStruct{ // ERROR "B, D are missing in ExhaustiveStruct"
|
||||
A: "a",
|
||||
c: false,
|
||||
E: time.Now(),
|
||||
}
|
||||
}
|
||||
|
176
test/testdata/exhaustivestruct_custom.go
vendored
176
test/testdata/exhaustivestruct_custom.go
vendored
@ -1,10 +1,10 @@
|
||||
//args: -Eexhaustivestruct
|
||||
//config: linters-settings.exhaustivestruct.struct-patterns=*.Test1,*.Test3
|
||||
// args: -Eexhaustivestruct --internal-cmd-test
|
||||
// config_path: testdata/configs/exhaustivestruct.yml
|
||||
package testdata
|
||||
|
||||
import "time"
|
||||
|
||||
type Test1 struct {
|
||||
type ExhaustiveStructCustom struct {
|
||||
A string
|
||||
B int
|
||||
c bool // private field inside the same package are not ignored
|
||||
@ -12,35 +12,42 @@ type Test1 struct {
|
||||
E time.Time
|
||||
}
|
||||
|
||||
var passTest1 = Test1{
|
||||
A: "a",
|
||||
B: 0,
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
func exhaustiveStructCustom() {
|
||||
// pass
|
||||
_ = ExhaustiveStructCustom{
|
||||
A: "a",
|
||||
B: 0,
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
// fail
|
||||
_ = ExhaustiveStructCustom{ // ERROR "B is missing in ExhaustiveStructCustom"
|
||||
A: "a",
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
// failMultiple
|
||||
_ = ExhaustiveStructCustom{ // ERROR "B, D are missing in ExhaustiveStructCustom"
|
||||
A: "a",
|
||||
c: false,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
// failPrivate
|
||||
_ = ExhaustiveStructCustom{ // ERROR "c is missing in ExhaustiveStructCustom"
|
||||
A: "a",
|
||||
B: 0,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var failTest1 = Test1{ // ERROR "B is missing in Test"
|
||||
A: "a",
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
var failMultipleTest1 = Test1{ // ERROR "B, D are missing in Test"
|
||||
A: "a",
|
||||
c: false,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
var failPrivateTest1 = Test1{ // ERROR "c is missing in Test"
|
||||
A: "a",
|
||||
B: 0,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
type Test2 struct {
|
||||
type ExhaustiveStructCustom1 struct {
|
||||
A string
|
||||
B int
|
||||
c bool // private field inside the same package are not ignored
|
||||
@ -48,35 +55,37 @@ type Test2 struct {
|
||||
E time.Time
|
||||
}
|
||||
|
||||
var passTest2 = Test1{
|
||||
A: "a",
|
||||
B: 0,
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
func exhaustiveStructCustom1() {
|
||||
_ = ExhaustiveStructCustom1{
|
||||
A: "a",
|
||||
B: 0,
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
_ = ExhaustiveStructCustom1{
|
||||
A: "a",
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
_ = ExhaustiveStructCustom1{
|
||||
A: "a",
|
||||
c: false,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
_ = ExhaustiveStructCustom1{
|
||||
A: "a",
|
||||
B: 0,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
var failTest2 = Test2{
|
||||
A: "a",
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
var failMultipleTest2 = Test2{
|
||||
A: "a",
|
||||
c: false,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
var failPrivateTest2 = Test2{
|
||||
A: "a",
|
||||
B: 0,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
type Test3 struct {
|
||||
type ExhaustiveStructCustom2 struct {
|
||||
A string
|
||||
B int
|
||||
c bool // private field inside the same package are not ignored
|
||||
@ -84,30 +93,37 @@ type Test3 struct {
|
||||
E time.Time
|
||||
}
|
||||
|
||||
var passTest3 = Test3{
|
||||
A: "a",
|
||||
B: 0,
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
func exhaustiveStructCustom2() {
|
||||
// pass
|
||||
_ = ExhaustiveStructCustom2{
|
||||
A: "a",
|
||||
B: 0,
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
var failTest3 = Test3{ // ERROR "B is missing in Test"
|
||||
A: "a",
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
// fail
|
||||
_ = ExhaustiveStructCustom2{ // ERROR "B is missing in ExhaustiveStructCustom2"
|
||||
A: "a",
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
var failMultipleTest3 = Test3{ // ERROR "B, D are missing in Test"
|
||||
A: "a",
|
||||
c: false,
|
||||
E: time.Now(),
|
||||
}
|
||||
// failMultiple
|
||||
_ = ExhaustiveStructCustom2{ // ERROR "B, D are missing in ExhaustiveStructCustom2"
|
||||
A: "a",
|
||||
c: false,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
// failPrivate
|
||||
_ = ExhaustiveStructCustom2{ // ERROR "c is missing in ExhaustiveStructCustom2"
|
||||
A: "a",
|
||||
B: 0,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
var failPrivateTest3 = Test3{ // ERROR "c is missing in Test"
|
||||
A: "a",
|
||||
B: 0,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
47
test/testdata/exhaustruct.go
vendored
Normal file
47
test/testdata/exhaustruct.go
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
// args: -Eexhaustruct
|
||||
package testdata
|
||||
|
||||
import "time"
|
||||
|
||||
type Exhaustruct struct {
|
||||
A string
|
||||
B int
|
||||
c bool // private field inside the same package are not ignored
|
||||
D float64
|
||||
E time.Time
|
||||
}
|
||||
|
||||
func exhaustruct() {
|
||||
// pass
|
||||
_ = Exhaustruct{
|
||||
A: "a",
|
||||
B: 0,
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
// failPrivate
|
||||
_ = Exhaustruct{ // ERROR "c is missing in Exhaustruct"
|
||||
A: "a",
|
||||
B: 0,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
// fail
|
||||
_ = Exhaustruct{ // ERROR "B is missing in Exhaustruct"
|
||||
A: "a",
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
// failMultiple
|
||||
_ = Exhaustruct{ // ERROR "B, D are missing in Exhaustruct"
|
||||
A: "a",
|
||||
c: false,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
}
|
133
test/testdata/exhaustruct_custom.go
vendored
Normal file
133
test/testdata/exhaustruct_custom.go
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
// args: -Eexhaustruct
|
||||
// config_path: testdata/configs/exhaustruct.yml
|
||||
package testdata
|
||||
|
||||
import "time"
|
||||
|
||||
type ExhaustructCustom struct {
|
||||
A string
|
||||
B int
|
||||
c bool // private field inside the same package are not ignored
|
||||
D float64
|
||||
E time.Time
|
||||
}
|
||||
|
||||
func exhaustructCustom() {
|
||||
// pass
|
||||
_ = ExhaustructCustom{
|
||||
A: "a",
|
||||
B: 0,
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
// fail
|
||||
_ = ExhaustructCustom{ // ERROR "B is missing in ExhaustructCustom"
|
||||
A: "a",
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
// failMultiple
|
||||
_ = ExhaustructCustom{ // ERROR "B, D are missing in ExhaustructCustom"
|
||||
A: "a",
|
||||
c: false,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
// failPrivate
|
||||
_ = ExhaustructCustom{ // ERROR "c is missing in ExhaustructCustom"
|
||||
A: "a",
|
||||
B: 0,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type ExhaustructCustom1 struct {
|
||||
A string
|
||||
B int
|
||||
c bool // private field inside the same package are not ignored
|
||||
D float64
|
||||
E time.Time
|
||||
}
|
||||
|
||||
func exhaustructCustom1() {
|
||||
// pass
|
||||
_ = ExhaustructCustom{
|
||||
A: "a",
|
||||
B: 0,
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
// fail
|
||||
_ = ExhaustructCustom1{
|
||||
A: "a",
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
// failMultiple
|
||||
_ = ExhaustructCustom1{
|
||||
A: "a",
|
||||
c: false,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
// failPrivate
|
||||
_ = ExhaustructCustom1{
|
||||
A: "a",
|
||||
B: 0,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type ExhaustructCustom2 struct {
|
||||
A string
|
||||
B int
|
||||
c bool // private field inside the same package are not ignored
|
||||
D float64
|
||||
E time.Time
|
||||
}
|
||||
|
||||
func exhaustructCustom2() {
|
||||
// pass
|
||||
_ = ExhaustructCustom2{
|
||||
A: "a",
|
||||
B: 0,
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
// fail
|
||||
_ = ExhaustructCustom2{
|
||||
A: "a",
|
||||
c: false,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
// failMultiple
|
||||
_ = ExhaustructCustom2{
|
||||
A: "a",
|
||||
c: false,
|
||||
E: time.Now(),
|
||||
}
|
||||
|
||||
// failPrivate
|
||||
_ = ExhaustructCustom2{
|
||||
A: "a",
|
||||
B: 0,
|
||||
D: 1.0,
|
||||
E: time.Now(),
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user