feat: add exhaustruct linter (#2667)

Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
This commit is contained in:
Anton Zinovyev 2022-04-30 13:48:16 +03:00 committed by GitHub
parent acceecf661
commit 380699a099
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 379 additions and 106 deletions

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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
View 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")

View 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)
}

View File

@ -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").

View File

@ -0,0 +1,5 @@
linters-settings:
exhaustivestruct:
struct-patterns:
- '*.ExhaustiveStructCustom'
- '*.ExhaustiveStructCustom2'

6
test/testdata/configs/exhaustruct.yml vendored Normal file
View File

@ -0,0 +1,6 @@
linters-settings:
exhaustruct:
include:
- .*\.ExhaustructCustom
exclude:
- .*\.ExhaustructCustom[\d]{1,2}

View File

@ -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(),
}
}

View File

@ -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
View 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
View 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(),
}
}