diff --git a/.golangci.next.reference.yml b/.golangci.next.reference.yml index a7d9017d28f6..cababbea228f 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 @@ -530,6 +532,14 @@ linters: # Default: false analyze-types: true + funcorder: + # Checks that constructors are placed after the structure declaration. + # Default: true + constructor: false + # Checks if the exported methods of a structure are placed before the non-exported ones. + # 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 f8f3e09fba62..becc53b6c41f 100644 --- a/go.mod +++ b/go.mod @@ -74,6 +74,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 64c32c957c21..edd4bc920e03 100644 --- a/go.sum +++ b/go.sum @@ -391,6 +391,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/jsonschema/golangci.next.jsonschema.json b/jsonschema/golangci.next.jsonschema.json index 5dc6b9d9570d..996a5ec3721f 100644 --- a/jsonschema/golangci.next.jsonschema.json +++ b/jsonschema/golangci.next.jsonschema.json @@ -741,6 +741,7 @@ "fatcontext", "forbidigo", "forcetypeassert", + "funcorder", "funlen", "ginkgolinter", "gocheckcompilerdirectives", @@ -1302,6 +1303,22 @@ } } }, + "funcorderSettings": { + "type": "object", + "additionalProperties": false, + "properties": { + "constructor": { + "description": "Checks that constructors are placed after the structure declaration.", + "type": "boolean", + "default": true + }, + "struct-method": { + "description": "Checks if the exported methods of a structure are placed before the non-exported ones.", + "type": "boolean", + "default": true + } + } + }, "funlenSettings": { "type": "object", "additionalProperties": false, @@ -4345,6 +4362,9 @@ "forbidigo": { "$ref": "#/definitions/settings/definitions/forbidigoSettings" }, + "funcorder": { + "$ref": "#/definitions/settings/definitions/funcorderSettings" + }, "funlen": { "$ref": "#/definitions/settings/definitions/funlenSettings" }, diff --git a/pkg/config/linters_settings.go b/pkg/config/linters_settings.go index 16269033c7e9..16a8bb9501b9 100644 --- a/pkg/config/linters_settings.go +++ b/pkg/config/linters_settings.go @@ -41,6 +41,10 @@ var defaultLintersSettings = LintersSettings{ Forbidigo: ForbidigoSettings{ ExcludeGodocExamples: true, }, + FuncOrder: FuncOrderSettings{ + Constructor: true, + StructMethod: true, + }, Funlen: FunlenSettings{ IgnoreComments: true, }, @@ -217,6 +221,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 +425,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..7b8e9689e587 --- /dev/null +++ b/pkg/golinters/funcorder/funcorder.go @@ -0,0 +1,29 @@ +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 { + cfg[a.Name] = map[string]any{ + analyzer.ConstructorCheckName: settings.Constructor, + analyzer.StructMethodCheckName: settings.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/golinters/funcorder/testdata/funcorder_disable_constructor.go b/pkg/golinters/funcorder/testdata/funcorder_disable_constructor.go new file mode 100644 index 000000000000..d131d5dee356 --- /dev/null +++ b/pkg/golinters/funcorder/testdata/funcorder_disable_constructor.go @@ -0,0 +1,41 @@ +//golangcitest:args -Efuncorder +//golangcitest:config_path testdata/funcorder_disable_constructor.yml +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 { + return &MyStruct2{Name: "John"} +} + +func NewTime() time.Time { + return time.Now() +} diff --git a/pkg/golinters/funcorder/testdata/funcorder_disable_constructor.yml b/pkg/golinters/funcorder/testdata/funcorder_disable_constructor.yml new file mode 100644 index 000000000000..df43fb02f4d4 --- /dev/null +++ b/pkg/golinters/funcorder/testdata/funcorder_disable_constructor.yml @@ -0,0 +1,6 @@ +version: "2" + +linters: + settings: + funcorder: + constructor: false diff --git a/pkg/golinters/funcorder/testdata/funcorder_struct_method.go b/pkg/golinters/funcorder/testdata/funcorder_struct_method.go new file mode 100644 index 000000000000..a44f980e8b9e --- /dev/null +++ b/pkg/golinters/funcorder/testdata/funcorder_struct_method.go @@ -0,0 +1,41 @@ +//golangcitest:args -Efuncorder +//golangcitest:config_path testdata/funcorder_struct_method.yml +package testdata + +import "time" + +type MyStruct struct { + Name string +} + +func (m MyStruct) lenName() int { + 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/golinters/funcorder/testdata/funcorder_struct_method.yml b/pkg/golinters/funcorder/testdata/funcorder_struct_method.yml new file mode 100644 index 000000000000..70b27b44f9cd --- /dev/null +++ b/pkg/golinters/funcorder/testdata/funcorder_struct_method.yml @@ -0,0 +1,6 @@ +version: "2" + +linters: + settings: + funcorder: + struct-method: false diff --git a/pkg/lint/lintersdb/builder_linter.go b/pkg/lint/lintersdb/builder_linter.go index a05e0bbd7f4c..5ac3e5ba2fb0 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" @@ -256,6 +257,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().