Skip to content
This repository has been archived by the owner on Jan 20, 2025. It is now read-only.

Commit

Permalink
FEATURE COMPLETE! 💥 (also Sorted methods support)
Browse files Browse the repository at this point in the history
This also includes a large refactoring to be feature complete in the
cleaner way possible.
  • Loading branch information
MarioCarrion committed Feb 13, 2019
1 parent 9ef688c commit 3039950
Show file tree
Hide file tree
Showing 23 changed files with 408 additions and 44 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ A really, really nitpicking linter that complains when the code is not organized
- [X] Section must be sorted: exported first, then unexported.
1. [X] `func` is the fifth section
- [X] Must be sorted, exported first, then unexported.
1. [ ] `func` method, is the sixth section
- [ ] Must be sorted by type, exported first, then unexported.
1. [X] `func` method, is the sixth section
- [X] Must be sorted by type, exported first, then unexported.

![code](code.png "code organization in file")

Expand Down
20 changes: 10 additions & 10 deletions break_comments.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const (
)

// NewBreakComments returns all the valid break-like comments.
func NewBreakComments(fset *token.FileSet, comments []*ast.CommentGroup) BreakComments {
func NewBreakComments(fset *token.FileSet, comments []*ast.CommentGroup) *BreakComments {
r := BreakComments{}

for _, c := range comments {
Expand All @@ -32,15 +32,7 @@ func NewBreakComments(fset *token.FileSet, comments []*ast.CommentGroup) BreakCo
}
}
}
return r
}

// Returns the next break line if any, when no more left it returns -1.
func (c *BreakComments) Next() int {
if c.index >= len(c.comments) {
return -1
}
return c.comments[c.index]
return &r
}

// Moves current line cursor to the received line.
Expand All @@ -56,3 +48,11 @@ func (c *BreakComments) MoveTo(line int) {
c.index++
}
}

// Returns the next break line if any, when no more left it returns -1.
func (c *BreakComments) Next() int {
if c.index >= len(c.comments) {
return -1
}
return c.comments[c.index]
}
2 changes: 2 additions & 0 deletions consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ type (
// * Group declaration is parenthesized
// * Declarations are sorted
func (c *ConstsValidator) Validate(v *ast.GenDecl, fset *token.FileSet) error { //nolint: gocyclo
c.identType = "Const"

if !v.Lparen.IsValid() {
return errors.Wrap(errors.New("expected parenthesized declaration"), fset.PositionFor(v.Pos(), false).String())
}
Expand Down
32 changes: 16 additions & 16 deletions file_sections.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type (
)

const (
// FileSectionImports defines the `import` state.
// FileSectionImports defines the `imports` state.
FileSectionImports FileSection = iota // FIXME kill

// FileSectionTypes defines the `type` state.
Expand Down Expand Up @@ -157,15 +157,15 @@ func (constsFileSection) Funcs() (FileSectionTransition, error) {
}

func (constsFileSection) Imports() (FileSectionTransition, error) {
return nil, errors.New("imports is invalid, next one must be vars, funcs or methods")
return nil, errors.New("`imports` is invalid, next one must be `vars`, `functions` or `methods`")
}

func (constsFileSection) Methods() (FileSectionTransition, error) {
return methodsFileSection{}, nil
}

func (constsFileSection) Types() (FileSectionTransition, error) {
return nil, errors.New("types is invalid, next one must be vars, funcs or methods")
return nil, errors.New("`types` is invalid, next one must be `vars`, `functions` or `methods`")
}

func (constsFileSection) Vars() (FileSectionTransition, error) {
Expand All @@ -175,27 +175,27 @@ func (constsFileSection) Vars() (FileSectionTransition, error) {
//-

func (funcsFileSection) Consts() (FileSectionTransition, error) {
return nil, errors.New("const is invalid, next one must be funcs or methods")
return nil, errors.New("`consts` is invalid, next one must be `functions` or `methods`")
}

func (funcsFileSection) Funcs() (FileSectionTransition, error) {
return funcsFileSection{}, nil
}

func (funcsFileSection) Imports() (FileSectionTransition, error) {
return nil, errors.New("imports is invalid, next one must be funcs or methods")
return nil, errors.New("`imports` is invalid, next one must be `functions` or `methods`")
}

func (funcsFileSection) Methods() (FileSectionTransition, error) {
return methodsFileSection{}, nil
}

func (funcsFileSection) Types() (FileSectionTransition, error) {
return nil, errors.New("type is invalid, next one must be funcs or methods")
return nil, errors.New("`types` is invalid, next one must be `functions` or `methods`")
}

func (funcsFileSection) Vars() (FileSectionTransition, error) {
return nil, errors.New("vars is invalid, next one must be funcs or methods")
return nil, errors.New("`vars` is invalid, next one must be `functions` or `methods`")
}

//-
Expand Down Expand Up @@ -227,27 +227,27 @@ func (importsFileSection) Vars() (FileSectionTransition, error) {
//-

func (methodsFileSection) Consts() (FileSectionTransition, error) {
return nil, errors.New("consts is invalid, next one must be methods")
return nil, errors.New("`consts` is invalid, next one must be `methods`")
}

func (methodsFileSection) Funcs() (FileSectionTransition, error) {
return nil, errors.New("funcs is invalid, next one must be methods")
return nil, errors.New("`functions` is invalid, next one must be `methods`")
}

func (methodsFileSection) Imports() (FileSectionTransition, error) {
return nil, errors.New("imports is invalid, next one must be methods")
return nil, errors.New("`imports` is invalid, next one must be `methods`")
}

func (methodsFileSection) Methods() (FileSectionTransition, error) {
return methodsFileSection{}, nil
}

func (methodsFileSection) Types() (FileSectionTransition, error) {
return nil, errors.New("types is invalid, next one must be methods")
return nil, errors.New("`types` is invalid, next one must be `methods`")
}

func (methodsFileSection) Vars() (FileSectionTransition, error) {
return nil, errors.New("vars is invalid, next one must be methods")
return nil, errors.New("`vars` is invalid, next one must be `methods`")
}

//-
Expand All @@ -261,7 +261,7 @@ func (typesFileSection) Funcs() (FileSectionTransition, error) {
}

func (typesFileSection) Imports() (FileSectionTransition, error) {
return nil, errors.New("imports is invalid, next one must be const, vars, funcs or method")
return nil, errors.New("`imports` is invalid, next one must be `const`, `vars`, `functions` or `methods`")
}

func (typesFileSection) Methods() (FileSectionTransition, error) {
Expand All @@ -279,23 +279,23 @@ func (typesFileSection) Vars() (FileSectionTransition, error) {
//-

func (varsFileSection) Consts() (FileSectionTransition, error) {
return nil, errors.New("types is invalid, next one must be func or methods")
return nil, errors.New("`consts` is invalid, next one must be `functions` or `methods`")
}

func (varsFileSection) Funcs() (FileSectionTransition, error) {
return funcsFileSection{}, nil
}

func (varsFileSection) Imports() (FileSectionTransition, error) {
return nil, errors.New("types is invalid, next one must be func or methods")
return nil, errors.New("`imports` is invalid, next one must be `functions` or `methods`")
}

func (varsFileSection) Methods() (FileSectionTransition, error) {
return methodsFileSection{}, nil
}

func (varsFileSection) Types() (FileSectionTransition, error) {
return nil, errors.New("types is invalid, next one must be func or methods")
return nil, errors.New("`types` is invalid, next one must be `functions` or `methods`")
}

func (varsFileSection) Vars() (FileSectionTransition, error) {
Expand Down
14 changes: 11 additions & 3 deletions funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@ type (
// functions.
FuncsValidator struct {
sortedNamesValidator
Comments *BreakComments

comments *BreakComments
lastLine int
}
)

// NewFuncsValidator returns a correctly initialized FuncsValidator.
func NewFuncsValidator(c *BreakComments) *FuncsValidator {
return &FuncsValidator{comments: c, sortedNamesValidator: sortedNamesValidator{identType: "Function"}}
}

// Validate makes sure the implemented function satisfies the following rules
// considering all previous declared functions:
// * Sorted exported functions are declared first,
Expand All @@ -26,15 +33,16 @@ func (f *FuncsValidator) Validate(v *ast.FuncDecl, fset *token.FileSet) error {
return err
}

if f.Comments.Next() > fset.PositionFor(v.Pos(), false).Line {
if f.lastLine != 0 && f.comments.Next() > f.lastLine {
f.last = ""
}

if err := f.validateSortedName(errPrefix, v.Name); err != nil {
return err
}

f.Comments.MoveTo(fset.PositionFor(v.End(), false).Line)
f.lastLine = fset.PositionFor(v.End(), false).Line
f.comments.MoveTo(f.lastLine)

return nil
}
9 changes: 7 additions & 2 deletions funcs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@ func TestFuncsValidator_Validate(t *testing.T) {
false,
},
{
"OK: groupd",
"OK: grouped",
"funcs_group.go",
false,
},
{
"OK: sorted",
"funcs_sorted_ok.go",
false,
},
{
"Error: sorted",
"funcs_sorted.go",
Expand All @@ -51,7 +56,7 @@ func TestFuncsValidator_Validate(t *testing.T) {
}

comments := nit.NewBreakComments(fset, f.Comments)
validator := nit.FuncsValidator{Comments: &comments}
validator := nit.NewFuncsValidator(comments)

for _, s := range f.Decls {
switch g := s.(type) {
Expand Down
6 changes: 3 additions & 3 deletions imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,21 +197,21 @@ func (externalImportsTransition) Local() (ImportsTransition, error) {
}

func (externalImportsTransition) Standard() (ImportsTransition, error) {
return nil, errors.New("standard is invalid, next one must be external or local.")
return nil, errors.New("standard imports is invalid, next one must be external or local.")
}

//-

func (localImportsTransition) External() (ImportsTransition, error) {
return nil, errors.New("external is invalid, next one must be local")
return nil, errors.New("external imports is invalid, next one must be local")
}

func (localImportsTransition) Local() (ImportsTransition, error) {
return localImportsTransition{}, nil
}

func (localImportsTransition) Standard() (ImportsTransition, error) {
return nil, errors.New("standard is invalid, next one must be local")
return nil, errors.New("standard imports is invalid, next one must be local")
}

//-
Expand Down
102 changes: 102 additions & 0 deletions methods.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package nit

import (
"fmt"
"go/ast"
"go/token"
"reflect"

"github.com/pkg/errors"
)

type (
// MethodsValidator defines the type including the rules used for validating
// methods.
MethodsValidator struct {
comments *BreakComments
sortedTypes sortedNamesValidator
sortedMethods sortedNamesValidator
types map[string]struct{}
lastLine int
lastType string
}

// TypesFound defines the type returning the types found in the file.
TypesFound interface {
Types() []string
}
)

// NewMethodsValidator returns a correctly initialized MethodsValidator.
func NewMethodsValidator(c *BreakComments, t TypesFound) (*MethodsValidator, error) {
if t == nil || reflect.ValueOf(t).IsNil() {
return nil, errors.New("no types found")
}

ts := make(map[string]struct{})
for _, tf := range t.Types() {
ts[tf] = struct{}{}
}

return &MethodsValidator{comments: c, types: ts}, nil
}

// Validate makes sure the implemented methods satisfies the following rules
// considering all previous declared methods:
// * Methods for exported types are declared first, then unexported ones,
// * Sorted exported methods are declared first,
// * Sorted unexported methods are declared next, and
// * Both groups can declare their own sorted subgroups.
func (m *MethodsValidator) Validate(v *ast.FuncDecl, fset *token.FileSet) error {
var rcvType *ast.Ident
switch e := v.Recv.List[0].Type.(type) {
case *ast.Ident:
rcvType = e
case *ast.StarExpr:
rcvType = e.X.(*ast.Ident)
}

errPrefix := fset.PositionFor(v.Pos(), false).String()
if _, ok := m.types[rcvType.Name]; !ok {
return errors.Wrap(errors.Errorf("Type `%s` is not defined in the file", rcvType.Name), errPrefix)
}

validateSorted := func(v *sortedNamesValidator, i *ast.Ident, honorComments bool) error {
if err := v.validateExported(errPrefix, i); err != nil {
return err
}

if honorComments {
next := m.comments.Next()

if m.lastLine != 0 && next > m.lastLine {
v.last = ""
}
}

if err := v.validateSortedName(errPrefix, i); err != nil {
return err
}

return nil
}

if m.lastType != rcvType.Name {
m.sortedTypes.identType = "Type"
if err := validateSorted(&m.sortedTypes, rcvType, false); err != nil {
return err
}
m.lastType = rcvType.Name
fmt.Println(rcvType.Name)
}

m.sortedMethods.identType = "Method"
if err := validateSorted(&m.sortedMethods, v.Name, true); err != nil {
return err
}

m.lastLine = fset.PositionFor(v.End(), false).Line
m.comments.MoveTo(m.lastLine)

return nil
}
Loading

0 comments on commit 3039950

Please sign in to comment.