Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add settings to enable or disable some rules on demand #18

Merged
Merged
32 changes: 19 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/manuelarte/funcorder)](https://goreportcard.com/report/github.com/manuelarte/funcorder)
![version](https://img.shields.io/github/v/release/manuelarte/funcorder)

- [🧐 FuncOrder](#-funcorder)
* [⬇️ Getting Started](#-getting-started)
* [🚀 Features](#-features)
+ [Check exported methods are placed before non-exported methods](#check-exported-methods-are-placed-before-non-exported-methods)
+ [Check `Constructors` functions are placed after struct declaration](#check-constructors-functions-are-placed-after-struct-declaration)
* [Resources](#resources)
- [⬇️ Getting Started](#-getting-started)
- [🚀 Features](#-features)
- [Check exported methods are placed before non-exported methods](#check-exported-methods-are-placed-before-non-exported-methods)
- [Check `Constructors` functions are placed after struct declaration](#check-constructors-functions-are-placed-after-struct-declaration)
- [Resources](#resources)

# 🧐 FuncOrder

Go Linter to check Functions/Methods Order.

## ⬇️ Getting Started
Expand All @@ -18,7 +20,12 @@ Install FuncOrder linter using

And then use it with

> funcorder ./...
> funcorder [-constructor-check=true|false] [-struct-method-check=true|false] ./...

Parameters:

- `constructor-check`: `true|false` (default `true`) Check that constructor is placed after struct declaration and before struct's methods.
- `struct-method-check`: `true|false` (default `true`) Check that exported struct's methods are declared before non-exported.

## 🚀 Features

Expand All @@ -33,17 +40,17 @@ This rule checks that the exported method are placed before the non-exported one

```go
type MyStruct struct {
Name string
Name string
}

// ❌ non-exported method
// placed before exported method
func (m MyStruct) lenName() int {
return len(m.Name)
return len(m.Name)
}

func (m MyStruct) GetName() string {
return m.Name
return m.Name
}
...
```
Expand All @@ -52,13 +59,13 @@ func (m MyStruct) GetName() string {

```go
type MyStruct struct {
Name string
Name string
}

// ✅ exported methods before
// non-exported methods
func (m MyStruct) GetName() string {
return m.Name
return m.Name
}

func (m MyStruct) lenName() int {
Expand All @@ -79,7 +86,6 @@ This rule checks that the `Constructor` functions are placed after the struct de
<details>
<summary>Constructor function</summary>

> [!NOTE]
> This linter considers a Constructor function a function that has the prefix *New*, or *Must*, and returns 1 or 2 types.
> Where the 1st return type is a struct declared in the same file.

Expand Down Expand Up @@ -129,4 +135,4 @@ func NewMyStruct() MyStruct {

## Resources

+ Following Uber Style Guidelines about [function-grouping-and-ordering](https://github.com/uber-go/guide/blob/master/style.md#function-grouping-and-ordering)
- Following Uber Style Guidelines about [function-grouping-and-ordering](https://github.com/uber-go/guide/blob/master/style.md#function-grouping-and-ordering)
39 changes: 27 additions & 12 deletions analyzer/analyzer.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
package analyzer

import (
"flag"
"go/ast"

"golang.org/x/tools/go/analysis"

"github.com/manuelarte/funcorder/internal/features"
"github.com/manuelarte/funcorder/internal/fileprocessor"
)

//nolint:gochecknoglobals // global variable
var flagSet flag.FlagSet

func NewAnalyzer() *analysis.Analyzer {
return &analysis.Analyzer{
Name: "funcorder",
Doc: "checks function order",
Run: run,
Flags: flagSet,
f := funcorder{}
a := &analysis.Analyzer{
Name: "funcorder",
Doc: "checks the order of functions, methods, and constructors",
Run: f.run,
}
a.Flags.BoolVar(&f.constructorCheck, "constructor-check", true,
"enable/disable feature to check constructors are placed after struct declaration")
a.Flags.BoolVar(&f.structMethodCheck, "struct-method-check", true,
"enable/disable feature to check whether the exported struct's methods "+
"are placed before the non-exported")

return a
}

func run(pass *analysis.Pass) (any, error) {
fp := fileprocessor.NewFileProcessor()
type funcorder struct {
constructorCheck bool
structMethodCheck bool
}

func (f *funcorder) run(pass *analysis.Pass) (any, error) {
var enabledCheckers features.Feature
if f.constructorCheck {
enabledCheckers |= features.ConstructorCheck
}
if f.structMethodCheck {
enabledCheckers |= features.StructMethodCheck
}
fp := fileprocessor.NewFileProcessor(enabledCheckers)
for _, file := range pass.Files {
ast.Inspect(file, func(n ast.Node) bool {
if _, ok := n.(*ast.File); ok {
Expand All @@ -40,6 +55,6 @@ func run(pass *analysis.Pass) (any, error) {
for _, err := range errs {
pass.Report(analysis.Diagnostic{Pos: err.GetPos(), Message: err.Error()})
}
//nolint:nilnil //interface{}, error
//nolint:nilnil //any, error
return nil, nil
}
22 changes: 22 additions & 0 deletions analyzer/analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,25 @@ import (
func TestAll(t *testing.T) {
analysistest.Run(t, analysistest.TestData(), analyzer.NewAnalyzer(), "simple")
}

func TestConstructorCheckOnly(t *testing.T) {
a := analyzer.NewAnalyzer()
if err := a.Flags.Set("constructor-check", "true"); err != nil {
t.Fatal(err)
}
if err := a.Flags.Set("struct-method-check", "false"); err != nil {
t.Fatal(err)
}
analysistest.Run(t, analysistest.TestData(), a, "constructor-check")
}

func TestStructMethodsCheckOnly(t *testing.T) {
a := analyzer.NewAnalyzer()
if err := a.Flags.Set("constructor-check", "false"); err != nil {
t.Fatal(err)
}
if err := a.Flags.Set("struct-method-check", "true"); err != nil {
t.Fatal(err)
}
analysistest.Run(t, analysistest.TestData(), a, "struct-method-check")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package simple

type MyStruct2 struct {
Name string
}

func (m MyStruct2) GetName() string {
return m.Name
}

func (m *MyStruct2) SetName(name string) {
m.Name = name
}

func NewOtherMyStruct2() (m *MyStruct2) { // want `constructor \"NewOtherMyStruct2\" for struct \"MyStruct2\" should be placed before struct method \"GetName\"`
m = &MyStruct2{Name: "John"}
return
}

func NewMyStruct2() *MyStruct2 { // want `constructor \"NewMyStruct2\" for struct \"MyStruct2\" should be placed before struct method \"GetName\"`
return &MyStruct2{Name: "John"}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package simple

func NewOtherMyStruct() (m *MyStruct) { // want "should be placed after the struct declaration"
m = &MyStruct{Name: "John"}
return
}

func NewMyStruct() *MyStruct { // want "should be placed after the struct declaration"
return &MyStruct{Name: "John"}
}

func MustMyStruct() *MyStruct { // want `function \"MustMyStruct\" for struct \"MyStruct\" should be placed after the struct declaration`
return NewMyStruct()
}

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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package simple

import (
"time"
)

func NewOtherWayMyStruct() MyStruct {
return MyStruct{Name: "John"}
}

func NewTimeStruct() time.Time {
return time.Now()
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package simple

//nolint:recvcheck // testing linter
type MyStruct2 struct {
Name string
}
Expand All @@ -13,7 +12,6 @@ func (m *MyStruct2) SetName(name string) {
m.Name = name
}

//nolint:nonamedreturns // testing linter
func NewOtherMyStruct2() (m *MyStruct2) { // want `constructor \"NewOtherMyStruct2\" for struct \"MyStruct2\" should be placed before struct method \"GetName\"`
m = &MyStruct2{Name: "John"}
return
Expand Down
2 changes: 0 additions & 2 deletions analyzer/testdata/src/simple/constructors_before_struct.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package simple

//nolint:nonamedreturns // testing linter
func NewOtherMyStruct() (m *MyStruct) { // want "should be placed after the struct declaration"
m = &MyStruct{Name: "John"}
return
Expand All @@ -14,7 +13,6 @@ func MustMyStruct() *MyStruct { // want `function \"MustMyStruct\" for struct \"
return NewMyStruct()
}

//nolint:recvcheck // testing linter
type MyStruct struct {
Name string
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package simple

type MyStruct2 struct {
Name string
}

func (m MyStruct2) GetName() string {
return m.Name
}

func (m *MyStruct2) SetName(name string) {
m.Name = name
}

func NewOtherMyStruct2() (m *MyStruct2) {
m = &MyStruct2{Name: "John"}
return
}

func NewMyStruct2() *MyStruct2 {
return &MyStruct2{Name: "John"}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package simple

func NewOtherMyStruct() (m *MyStruct) {
m = &MyStruct{Name: "John"}
return
}

func NewMyStruct() *MyStruct {
return &MyStruct{Name: "John"}
}

func MustMyStruct() *MyStruct {
return NewMyStruct()
}

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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package simple

import (
"time"
)

func NewOtherWayMyStruct() MyStruct {
return MyStruct{Name: "John"}
}

func NewTimeStruct() time.Time {
return time.Now()
}
12 changes: 12 additions & 0 deletions internal/features/features.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package features

const (
ConstructorCheck Feature = 1 << iota
StructMethodCheck
)

type Feature uint8

func (c Feature) IsEnabled(other Feature) bool {
return c&other != 0
}
11 changes: 7 additions & 4 deletions internal/fileprocessor/file_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ import (

"github.com/manuelarte/funcorder/internal/astutils"
"github.com/manuelarte/funcorder/internal/errors"
"github.com/manuelarte/funcorder/internal/features"
"github.com/manuelarte/funcorder/internal/models"
)

// FileProcessor Holder to store all the functions that are potential to be constructors and all the structs.
type FileProcessor struct {
structs map[string]*models.StructHolder
structs map[string]*models.StructHolder
features features.Feature
}

// NewFileProcessor creates a new file processor.
func NewFileProcessor() *FileProcessor {
func NewFileProcessor(checkers features.Feature) *FileProcessor {
return &FileProcessor{
structs: make(map[string]*models.StructHolder),
structs: make(map[string]*models.StructHolder),
features: checkers,
}
}

Expand Down Expand Up @@ -81,7 +84,7 @@ func (fp *FileProcessor) getOrCreate(structName string) *models.StructHolder {
if holder, ok := fp.structs[structName]; ok {
return holder
}
created := &models.StructHolder{}
created := &models.StructHolder{Features: fp.features}
fp.structs[structName] = created
return created
}
Loading