Skip to content

Commit a39e032

Browse files
authored
Add funcorder linter (#5630)
1 parent e0e6eae commit a39e032

13 files changed

+222
-0
lines changed

.golangci.next.reference.yml

+10
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ linters:
4343
- fatcontext
4444
- forbidigo
4545
- forcetypeassert
46+
- funcorder
4647
- funlen
4748
- ginkgolinter
4849
- gocheckcompilerdirectives
@@ -150,6 +151,7 @@ linters:
150151
- fatcontext
151152
- forbidigo
152153
- forcetypeassert
154+
- funcorder
153155
- funlen
154156
- ginkgolinter
155157
- gocheckcompilerdirectives
@@ -530,6 +532,14 @@ linters:
530532
# Default: false
531533
analyze-types: true
532534

535+
funcorder:
536+
# Checks that constructors are placed after the structure declaration.
537+
# Default: true
538+
constructor: false
539+
# Checks if the exported methods of a structure are placed before the non-exported ones.
540+
# Default: true
541+
struct-method: false
542+
533543
funlen:
534544
# Checks the number of lines in a function.
535545
# If lower than 0, disable the check.

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ require (
7474
github.com/ldez/usetesting v0.4.2
7575
github.com/leonklingele/grouper v1.1.2
7676
github.com/macabu/inamedparam v0.2.0
77+
github.com/manuelarte/funcorder v0.2.1
7778
github.com/maratori/testableexamples v1.0.0
7879
github.com/maratori/testpackage v1.1.1
7980
github.com/matoous/godox v1.1.0

go.sum

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jsonschema/golangci.next.jsonschema.json

+20
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,7 @@
741741
"fatcontext",
742742
"forbidigo",
743743
"forcetypeassert",
744+
"funcorder",
744745
"funlen",
745746
"ginkgolinter",
746747
"gocheckcompilerdirectives",
@@ -1302,6 +1303,22 @@
13021303
}
13031304
}
13041305
},
1306+
"funcorderSettings": {
1307+
"type": "object",
1308+
"additionalProperties": false,
1309+
"properties": {
1310+
"constructor": {
1311+
"description": "Checks that constructors are placed after the structure declaration.",
1312+
"type": "boolean",
1313+
"default": true
1314+
},
1315+
"struct-method": {
1316+
"description": "Checks if the exported methods of a structure are placed before the non-exported ones.",
1317+
"type": "boolean",
1318+
"default": true
1319+
}
1320+
}
1321+
},
13051322
"funlenSettings": {
13061323
"type": "object",
13071324
"additionalProperties": false,
@@ -4345,6 +4362,9 @@
43454362
"forbidigo": {
43464363
"$ref": "#/definitions/settings/definitions/forbidigoSettings"
43474364
},
4365+
"funcorder": {
4366+
"$ref": "#/definitions/settings/definitions/funcorderSettings"
4367+
},
43484368
"funlen": {
43494369
"$ref": "#/definitions/settings/definitions/funlenSettings"
43504370
},

pkg/config/linters_settings.go

+10
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ var defaultLintersSettings = LintersSettings{
4141
Forbidigo: ForbidigoSettings{
4242
ExcludeGodocExamples: true,
4343
},
44+
FuncOrder: FuncOrderSettings{
45+
Constructor: true,
46+
StructMethod: true,
47+
},
4448
Funlen: FunlenSettings{
4549
IgnoreComments: true,
4650
},
@@ -217,6 +221,7 @@ type LintersSettings struct {
217221
Exhaustruct ExhaustructSettings `mapstructure:"exhaustruct"`
218222
Fatcontext FatcontextSettings `mapstructure:"fatcontext"`
219223
Forbidigo ForbidigoSettings `mapstructure:"forbidigo"`
224+
FuncOrder FuncOrderSettings `mapstructure:"funcorder"`
220225
Funlen FunlenSettings `mapstructure:"funlen"`
221226
GinkgoLinter GinkgoLinterSettings `mapstructure:"ginkgolinter"`
222227
Gocognit GocognitSettings `mapstructure:"gocognit"`
@@ -420,6 +425,11 @@ type ForbidigoPattern struct {
420425
Msg string `yaml:"msg,omitempty" mapstructure:"msg,omitempty"`
421426
}
422427

428+
type FuncOrderSettings struct {
429+
Constructor bool `mapstructure:"constructor,omitempty"`
430+
StructMethod bool `mapstructure:"struct-method,omitempty"`
431+
}
432+
423433
type FunlenSettings struct {
424434
Lines int `mapstructure:"lines"`
425435
Statements int `mapstructure:"statements"`

pkg/golinters/funcorder/funcorder.go

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package funcorder
2+
3+
import (
4+
"github.com/manuelarte/funcorder/analyzer"
5+
"golang.org/x/tools/go/analysis"
6+
7+
"github.com/golangci/golangci-lint/v2/pkg/config"
8+
"github.com/golangci/golangci-lint/v2/pkg/goanalysis"
9+
)
10+
11+
func New(settings *config.FuncOrderSettings) *goanalysis.Linter {
12+
a := analyzer.NewAnalyzer()
13+
14+
cfg := map[string]map[string]any{}
15+
16+
if settings != nil {
17+
cfg[a.Name] = map[string]any{
18+
analyzer.ConstructorCheckName: settings.Constructor,
19+
analyzer.StructMethodCheckName: settings.StructMethod,
20+
}
21+
}
22+
23+
return goanalysis.NewLinter(
24+
a.Name,
25+
a.Doc,
26+
[]*analysis.Analyzer{a},
27+
cfg,
28+
).WithLoadMode(goanalysis.LoadModeSyntax)
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package funcorder
2+
3+
import (
4+
"testing"
5+
6+
"github.com/golangci/golangci-lint/v2/test/testshared/integration"
7+
)
8+
9+
func TestFromTestdata(t *testing.T) {
10+
integration.RunTestdata(t)
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//golangcitest:args -Efuncorder
2+
package testdata
3+
4+
import "time"
5+
6+
type MyStruct struct {
7+
Name string
8+
}
9+
10+
func (m MyStruct) lenName() int { // want `unexported method "lenName" for struct "MyStruct" should be placed after the exported method "SetName"`
11+
return len(m.Name)
12+
}
13+
14+
func (m MyStruct) GetName() string {
15+
return m.Name
16+
}
17+
18+
func (m *MyStruct) SetName(name string) {
19+
m.Name = name
20+
}
21+
22+
type MyStruct2 struct {
23+
Name string
24+
}
25+
26+
func (m MyStruct2) GetName() string {
27+
return m.Name
28+
}
29+
30+
func (m *MyStruct2) SetName(name string) {
31+
m.Name = name
32+
}
33+
34+
func NewMyStruct2() *MyStruct2 { // want `constructor "NewMyStruct2" for struct "MyStruct2" should be placed before struct method "GetName"`
35+
return &MyStruct2{Name: "John"}
36+
}
37+
38+
func NewTime() time.Time {
39+
return time.Now()
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//golangcitest:args -Efuncorder
2+
//golangcitest:config_path testdata/funcorder_disable_constructor.yml
3+
package testdata
4+
5+
import "time"
6+
7+
type MyStruct struct {
8+
Name string
9+
}
10+
11+
func (m MyStruct) lenName() int { // want `unexported method "lenName" for struct "MyStruct" should be placed after the exported method "SetName"`
12+
return len(m.Name)
13+
}
14+
15+
func (m MyStruct) GetName() string {
16+
return m.Name
17+
}
18+
19+
func (m *MyStruct) SetName(name string) {
20+
m.Name = name
21+
}
22+
23+
type MyStruct2 struct {
24+
Name string
25+
}
26+
27+
func (m MyStruct2) GetName() string {
28+
return m.Name
29+
}
30+
31+
func (m *MyStruct2) SetName(name string) {
32+
m.Name = name
33+
}
34+
35+
func NewMyStruct2() *MyStruct2 {
36+
return &MyStruct2{Name: "John"}
37+
}
38+
39+
func NewTime() time.Time {
40+
return time.Now()
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: "2"
2+
3+
linters:
4+
settings:
5+
funcorder:
6+
constructor: false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//golangcitest:args -Efuncorder
2+
//golangcitest:config_path testdata/funcorder_struct_method.yml
3+
package testdata
4+
5+
import "time"
6+
7+
type MyStruct struct {
8+
Name string
9+
}
10+
11+
func (m MyStruct) lenName() int {
12+
return len(m.Name)
13+
}
14+
15+
func (m MyStruct) GetName() string {
16+
return m.Name
17+
}
18+
19+
func (m *MyStruct) SetName(name string) {
20+
m.Name = name
21+
}
22+
23+
type MyStruct2 struct {
24+
Name string
25+
}
26+
27+
func (m MyStruct2) GetName() string {
28+
return m.Name
29+
}
30+
31+
func (m *MyStruct2) SetName(name string) {
32+
m.Name = name
33+
}
34+
35+
func NewMyStruct2() *MyStruct2 { // want `constructor "NewMyStruct2" for struct "MyStruct2" should be placed before struct method "GetName"`
36+
return &MyStruct2{Name: "John"}
37+
}
38+
39+
func NewTime() time.Time {
40+
return time.Now()
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: "2"
2+
3+
linters:
4+
settings:
5+
funcorder:
6+
struct-method: false

pkg/lint/lintersdb/builder_linter.go

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/golangci/golangci-lint/v2/pkg/golinters/fatcontext"
3030
"github.com/golangci/golangci-lint/v2/pkg/golinters/forbidigo"
3131
"github.com/golangci/golangci-lint/v2/pkg/golinters/forcetypeassert"
32+
"github.com/golangci/golangci-lint/v2/pkg/golinters/funcorder"
3233
"github.com/golangci/golangci-lint/v2/pkg/golinters/funlen"
3334
"github.com/golangci/golangci-lint/v2/pkg/golinters/gci"
3435
"github.com/golangci/golangci-lint/v2/pkg/golinters/ginkgolinter"
@@ -256,6 +257,10 @@ func (LinterBuilder) Build(cfg *config.Config) ([]*linter.Config, error) {
256257
WithLoadForGoAnalysis().
257258
WithURL("https://github.com/gostaticanalysis/forcetypeassert"),
258259

260+
linter.NewConfig(funcorder.New(&cfg.Linters.Settings.FuncOrder)).
261+
WithSince("v2.1.0").
262+
WithURL("https://github.com/manuelarte/funcorder"),
263+
259264
linter.NewConfig(fatcontext.New(&cfg.Linters.Settings.Fatcontext)).
260265
WithSince("v1.58.0").
261266
WithLoadForGoAnalysis().

0 commit comments

Comments
 (0)