diff --git a/.golangci.next.reference.yml b/.golangci.next.reference.yml index 52a474c759b3..e8236e64a803 100644 --- a/.golangci.next.reference.yml +++ b/.golangci.next.reference.yml @@ -43,6 +43,7 @@ linters: - fatcontext - forbidigo - forcetypeassert + - funcorder - funlen - ginkgolinter - gocheckcompilerdirectives @@ -150,6 +151,7 @@ linters: - fatcontext - forbidigo - forcetypeassert + - funcorder - funlen - ginkgolinter - gocheckcompilerdirectives @@ -529,6 +531,14 @@ linters: # Default: false analyze-types: true + funcorder: + # Enable/disable feature to check constructors are placed after struct declaration. + # Default: true + constructor: false + # Enable/disable feature to check whether the exported struct's methods are placed before the non-exported. + # Default: true + struct-method: false + funlen: # Checks the number of lines in a function. # If lower than 0, disable the check. diff --git a/go.mod b/go.mod index 65ad99e18cfa..6f31422c7a85 100644 --- a/go.mod +++ b/go.mod @@ -73,6 +73,7 @@ require ( github.com/ldez/usetesting v0.4.2 github.com/leonklingele/grouper v1.1.2 github.com/macabu/inamedparam v0.2.0 + github.com/manuelarte/funcorder v0.2.1 github.com/maratori/testableexamples v1.0.0 github.com/maratori/testpackage v1.1.1 github.com/matoous/godox v1.1.0 diff --git a/go.sum b/go.sum index e06df5d5f89c..b37a75f59384 100644 --- a/go.sum +++ b/go.sum @@ -389,6 +389,8 @@ github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddB github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/manuelarte/funcorder v0.2.1 h1:7QJsw3qhljoZ5rH0xapIvjw31EcQeFbF31/7kQ/xS34= +github.com/manuelarte/funcorder v0.2.1/go.mod h1:BQQ0yW57+PF9ZpjpeJDKOffEsQbxDFKW8F8zSMe/Zd0= github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index 86ee4fea48d2..473512f7df0f 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -217,6 +217,7 @@ type LintersSettings struct { Exhaustruct ExhaustructSettings `mapstructure:"exhaustruct"` Fatcontext FatcontextSettings `mapstructure:"fatcontext"` Forbidigo ForbidigoSettings `mapstructure:"forbidigo"` + FuncOrder FuncOrderSettings `mapstructure:"funcorder"` Funlen FunlenSettings `mapstructure:"funlen"` GinkgoLinter GinkgoLinterSettings `mapstructure:"ginkgolinter"` Gocognit GocognitSettings `mapstructure:"gocognit"` @@ -420,6 +421,11 @@ type ForbidigoPattern struct { Msg string `yaml:"msg,omitempty" mapstructure:"msg,omitempty"` } +type FuncOrderSettings struct { + Constructor *bool `mapstructure:"constructor,omitempty"` + StructMethod *bool `mapstructure:"struct-method,omitempty"` +} + type FunlenSettings struct { Lines int `mapstructure:"lines"` Statements int `mapstructure:"statements"` diff --git a/pkg/golinters/funcorder/funcorder.go b/pkg/golinters/funcorder/funcorder.go new file mode 100644 index 000000000000..4d4431e76381 --- /dev/null +++ b/pkg/golinters/funcorder/funcorder.go @@ -0,0 +1,38 @@ +package funcorder + +import ( + "github.com/manuelarte/funcorder/analyzer" + "golang.org/x/tools/go/analysis" + + "github.com/golangci/golangci-lint/v2/pkg/config" + "github.com/golangci/golangci-lint/v2/pkg/goanalysis" +) + +func New(settings *config.FuncOrderSettings) *goanalysis.Linter { + a := analyzer.NewAnalyzer() + + cfg := map[string]map[string]any{} + + if settings != nil { + constructor := true + if settings.Constructor != nil { + constructor = *settings.Constructor + } + structMethod := true + if settings.StructMethod != nil { + structMethod = *settings.StructMethod + } + + cfg[a.Name] = map[string]any{ + analyzer.ConstructorCheckName: constructor, + analyzer.StructMethodCheckName: structMethod, + } + } + + return goanalysis.NewLinter( + a.Name, + a.Doc, + []*analysis.Analyzer{a}, + cfg, + ).WithLoadMode(goanalysis.LoadModeSyntax) +} diff --git a/pkg/golinters/funcorder/funcorder_integration_test.go b/pkg/golinters/funcorder/funcorder_integration_test.go new file mode 100644 index 000000000000..31ee6ec82d4b --- /dev/null +++ b/pkg/golinters/funcorder/funcorder_integration_test.go @@ -0,0 +1,11 @@ +package funcorder + +import ( + "testing" + + "github.com/golangci/golangci-lint/v2/test/testshared/integration" +) + +func TestFromTestdata(t *testing.T) { + integration.RunTestdata(t) +} diff --git a/pkg/golinters/funcorder/testdata/funcorder.go b/pkg/golinters/funcorder/testdata/funcorder.go new file mode 100644 index 000000000000..b9ae4e81fafa --- /dev/null +++ b/pkg/golinters/funcorder/testdata/funcorder.go @@ -0,0 +1,40 @@ +//golangcitest:args -Efuncorder +package testdata + +import "time" + +type MyStruct struct { + Name string +} + +func (m MyStruct) lenName() int { // want `unexported method "lenName" for struct "MyStruct" should be placed after the exported method "SetName"` + return len(m.Name) +} + +func (m MyStruct) GetName() string { + return m.Name +} + +func (m *MyStruct) SetName(name string) { + m.Name = name +} + +type MyStruct2 struct { + Name string +} + +func (m MyStruct2) GetName() string { + return m.Name +} + +func (m *MyStruct2) SetName(name string) { + m.Name = name +} + +func NewMyStruct2() *MyStruct2 { // want `constructor "NewMyStruct2" for struct "MyStruct2" should be placed before struct method "GetName"` + return &MyStruct2{Name: "John"} +} + +func NewTime() time.Time { + return time.Now() +} diff --git a/pkg/lint/lintersdb/builder_linter.go b/pkg/lint/lintersdb/builder_linter.go index 7e8e4ad1ba51..7e99d975edb6 100644 --- a/pkg/lint/lintersdb/builder_linter.go +++ b/pkg/lint/lintersdb/builder_linter.go @@ -29,6 +29,7 @@ import ( "github.com/golangci/golangci-lint/v2/pkg/golinters/fatcontext" "github.com/golangci/golangci-lint/v2/pkg/golinters/forbidigo" "github.com/golangci/golangci-lint/v2/pkg/golinters/forcetypeassert" + "github.com/golangci/golangci-lint/v2/pkg/golinters/funcorder" "github.com/golangci/golangci-lint/v2/pkg/golinters/funlen" "github.com/golangci/golangci-lint/v2/pkg/golinters/gci" "github.com/golangci/golangci-lint/v2/pkg/golinters/ginkgolinter" @@ -254,6 +255,10 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) { WithLoadForGoAnalysis(). WithURL("https://github.com/gostaticanalysis/forcetypeassert"), + linter.NewConfig(funcorder.New(&cfg.Linters.Settings.FuncOrder)). + WithSince("v2.1.0"). + WithURL("https://github.com/manuelarte/funcorder"), + linter.NewConfig(fatcontext.New(&cfg.Linters.Settings.Fatcontext)). WithSince("v1.58.0"). WithLoadForGoAnalysis().