Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions checkers/testdata/typeDefFirst/negative_tests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package checker_test

type rec struct{}

func (r rec) Method() {}
func (r *rec) MethodWithRef() {}

// Considering no type declaration in file it is also ok
func (r recv) Method2() {}

func JustFunction() {}
6 changes: 6 additions & 0 deletions checkers/testdata/typeDefFirst/positive_tests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package checker_test

func (r recv) MethodBefore() {}

/*! definition of type 'recv' should appear before its methods */
type recv struct{}
6 changes: 6 additions & 0 deletions checkers/testdata/typeDefFirst/positive_tests2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package checker_test

func (r *reciv) Method() {}

/*! definition of type 'reciv' should appear before its methods */
type reciv struct{ x, y int }
80 changes: 80 additions & 0 deletions checkers/typeDefFirst_checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package checkers

import (
"go/ast"
"go/token"

"github.com/go-critic/go-critic/checkers/internal/astwalk"
"github.com/go-critic/go-critic/framework/linter"
)

func init() {
var info linter.CheckerInfo
info.Name = "typeDefFirst"
info.Tags = []string{"style", "experimental"}
info.Summary = "File-scoped checker, that requires type definition before its method definitions"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File-scoped checker I'm curious - should be use this phrase in other checkers? maybe another tag or subtype? @quasilyte :)

info.Before = `
func (r rec) Method() {}
type rec struct{}
`
info.After = `
type rec struct{}
func (r rec) Method() {}
`
collection.AddChecker(&info, func(ctx *linter.CheckerContext) linter.FileWalker {
return &typeDefFirstChecker{
ctx: ctx,
}
})
}

type typeDefFirstChecker struct {
astwalk.WalkHandler
ctx *linter.CheckerContext
}

func (c *typeDefFirstChecker) WalkFile(f *ast.File) {
if f.Decls == nil {
return
}

typeUsageMap := make(map[string]bool)
for _, declaration := range f.Decls {
switch decl := declaration.(type) {
case *ast.FuncDecl:
if decl.Recv != nil {
receiver := decl.Recv.List[0]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should I check the array for not nilness? as far as I understood, in this case (where its receiver) there are always should be some fields, but the safer the better?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can add a simple func in golden files if it fails - better to add :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not fail, because it goes after decl.Recv != nil where Recv is recivier

typeName := c.receiverType(receiver.Type)
typeUsageMap[typeName] = true
}

case *ast.GenDecl:
if decl.Tok != token.TYPE {
continue
}
for _, spec := range decl.Specs {
if spec, ok := spec.(*ast.TypeSpec); ok {
typeName := spec.Name.Name
if val, ok := typeUsageMap[typeName]; ok && val {
c.warn(decl, typeName)
}
}
}
}
}
}

func (c *typeDefFirstChecker) warn(cause ast.Node, typeName string) {
c.ctx.Warn(cause, "definition of type '%s' should appear before its methods", typeName)
}

func (c *typeDefFirstChecker) receiverType(e ast.Expr) string {
switch e := e.(type) {
case *ast.StarExpr:
return c.receiverType(e.X)
case *ast.Ident:
return e.Name
default:
panic("unreachable")
}
}