Skip to content

Commit

Permalink
feat: add exhaustruct linter
Browse files Browse the repository at this point in the history
This linter can be called a successor of `exhaustivestruct`, and:

- it is at least **2.5+ times faster**, due to better algorithm;
- can receive `include` and/or `exclude` patterns;
- expects received patterns to be RegExp, therefore this package is not api-compatible with `exhaustivestruct`.

Also: deprecate `exhaustivestruct` linter
  • Loading branch information
xobotyi committed Mar 30, 2022
1 parent bb2fb45 commit 6791ae1
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 1 deletion.
10 changes: 10 additions & 0 deletions .golangci.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
# List of regular expressions to exclude struct packages and names from check.
exclude:
- 'cobra\.Command$'

forbidigo:
# Forbid the following identifiers (list of regexp).
forbid:
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ require (
mvdan.cc/unparam v0.0.0-20211214103731-d0ef000c54e5
)

require github.com/GaijinEntertainment/go-exhaustruct v1.0.0

require (
github.com/Masterminds/semver v1.5.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions pkg/config/linters_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ type LintersSettings struct {
ErrorLint ErrorLintSettings
Exhaustive ExhaustiveSettings
ExhaustiveStruct ExhaustiveStructSettings
Exhaustruct ExhaustructSettings
Forbidigo ForbidigoSettings
Funlen FunlenSettings
Gci GciSettings
Expand Down Expand Up @@ -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"`
Expand Down
32 changes: 32 additions & 0 deletions pkg/golinters/exhaustruct.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package golinters

import (
"strings"

"github.com/GaijinEntertainment/go-exhaustruct/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 {
a := analyzer.Analyzer

var cfg map[string]map[string]interface{}
if settings != nil {
cfg = map[string]map[string]interface{}{
a.Name: {
"include": strings.Join(settings.Include, ","),
"exclude": strings.Join(settings.Exclude, ","),
},
}
}

return goanalysis.NewLinter(
a.Name,
a.Doc,
[]*analysis.Analyzer{a},
cfg,
).WithLoadMode(goanalysis.LoadModeTypesInfo)
}
11 changes: 10 additions & 1 deletion pkg/lint/lintersdb/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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("Owner seems to abandon 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").
Expand Down
40 changes: 40 additions & 0 deletions test/testdata/exhaustruct.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//args: -Eexhaustruct
package testdata

import "time"

type Test struct {
A string
B int
c bool // private field inside the same package are not ignored
D float64
E time.Time
}

var pass = Test{
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(),
}

var fail = Test{ // ERROR "B is missing in Test"
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(),
}
114 changes: 114 additions & 0 deletions test/testdata/exhaustruct_custom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//args: -Eexhaustruct
//config: linters-settings.exhaustruct.include=.*\.Test1$
//config: linters-settings.exhaustruct.exclude=.*\.Test3$
package testdata

import "time"

type Test1 struct {
A string
B int
c bool // private field inside the same package are not ignored
D float64
E time.Time
}

var passTest1 = Test1{
A: "a",
B: 0,
c: false,
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 {
A string
B int
c bool // private field inside the same package are not ignored
D float64
E time.Time
}

var passTest2 = Test1{
A: "a",
B: 0,
c: false,
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 {
A string
B int
c bool // private field inside the same package are not ignored
D float64
E time.Time
}

var passTest3 = Test3{
A: "a",
B: 0,
c: false,
D: 1.0,
E: time.Now(),
}

var failTest3 = Test3{
A: "a",
c: false,
D: 1.0,
E: time.Now(),
}

var failMultipleTest3 = Test3{
A: "a",
c: false,
E: time.Now(),
}

var failPrivateTest3 = Test3{
A: "a",
B: 0,
D: 1.0,
E: time.Now(),
}

0 comments on commit 6791ae1

Please sign in to comment.