From 45ae9f049483ea3807969ef534025a0d63518f33 Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Mon, 31 Jul 2023 11:31:07 +0200 Subject: [PATCH] Feature: Ignore comments in getLines() (#14) * Add go.mod * Add basic unittest * Add option to ignore comments in getLines() * Extend test coverage --- go.mod | 3 ++ main.go | 28 ++++++++++++-- main_test.go | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 go.mod create mode 100644 main_test.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..17fa745 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/ultraware/funlen + +go 1.20 diff --git a/main.go b/main.go index 2ba3530..b68ddb9 100644 --- a/main.go +++ b/main.go @@ -13,7 +13,7 @@ const ( ) // Run runs this linter on the provided code -func Run(file *ast.File, fset *token.FileSet, lineLimit, stmtLimit int) []Message { +func Run(file *ast.File, fset *token.FileSet, lineLimit int, stmtLimit int, ignoreComments bool) []Message { if lineLimit == 0 { lineLimit = defaultLineLimit } @@ -21,6 +21,8 @@ func Run(file *ast.File, fset *token.FileSet, lineLimit, stmtLimit int) []Messag stmtLimit = defaultStmtLimit } + cmap := ast.NewCommentMap(fset, file, file.Comments) + var msgs []Message for _, f := range file.Decls { decl, ok := f.(*ast.FuncDecl) @@ -36,7 +38,7 @@ func Run(file *ast.File, fset *token.FileSet, lineLimit, stmtLimit int) []Messag } if lineLimit > 0 { - if lines := getLines(fset, decl); lines > lineLimit { + if lines := getLines(fset, decl, cmap.Filter(decl), ignoreComments); lines > lineLimit { msgs = append(msgs, makeLineMessage(fset, decl.Name, lines, lineLimit)) } } @@ -65,8 +67,26 @@ func makeStmtMessage(fset *token.FileSet, funcInfo *ast.Ident, stmts, stmtLimit } } -func getLines(fset *token.FileSet, f *ast.FuncDecl) int { // nolint: interfacer - return fset.Position(f.End()).Line - fset.Position(f.Pos()).Line - 1 +func getLines(fset *token.FileSet, f *ast.FuncDecl, cmap ast.CommentMap, ignoreComments bool) int { // nolint: interfacer + var lineCount int + var commentCount int + + lineCount = fset.Position(f.End()).Line - fset.Position(f.Pos()).Line - 1 + + if !ignoreComments { + return lineCount + } + + for _, c := range cmap.Comments() { + // If the CommenGroup's lines are inside the function + // count how many comments are in the CommentGroup + if (fset.Position(c.Pos()).Line > fset.Position(f.Pos()).Line) && + (fset.Position(c.End()).Line < fset.Position(f.End()).Line) { + commentCount += len(c.List) + } + } + + return lineCount - commentCount } func parseStmts(s []ast.Stmt) (total int) { diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..1e0ad9c --- /dev/null +++ b/main_test.go @@ -0,0 +1,105 @@ +package funlen + +import ( + "go/parser" + "go/token" + "strings" + "testing" +) + +func TestRunTable(t *testing.T) { + testcases := map[string]struct { + input string + expected string + lineLimit int + stmtLimit int + }{ + "too-many-statements": { + input: `package main + func main() { + print("Hello, world!") + print("Hello, world!")}`, + expected: "Function 'main' has too many statements (2 > 1)", + lineLimit: 1, + stmtLimit: 1, + }, + "too-many-lines": { + input: `package main + import "fmt" + func main() { + print("main!") + print("is!") + print("too!") + print("long")}`, + expected: "Function 'main' is too long (3 > 1)", + lineLimit: 1, + stmtLimit: 10, + }, + "too-many-statements-inline-func": { + input: `package main + func main() { + print("Hello, world!") + if true { + y := []int{1,2,3,4} + for k, v := range y { + f := func() { print("test") } + f() + } + } + print("Hello, world!")}`, + expected: "Function 'main' has too many statements (8 > 1)", + lineLimit: 1, + stmtLimit: 1, + }, + } + + for name, test := range testcases { + t.Run(name, func(t *testing.T) { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "", test.input, parser.ParseComments) + + if err != nil { + t.Error("\nActual: ", err, "\n Did not expected error") + } + + r := Run(f, fset, test.lineLimit, test.stmtLimit, false) + actual := r[0].Message + + if !strings.Contains(actual, test.expected) { + t.Error("\nActual: ", actual, "\nExpected: ", test.expected) + } + }) + } +} + +func TestRunIgnoresComments(t *testing.T) { + + input := `package main + func main() { + // Comment 1 + // Comment 2 + // Comment 3 + print("Hello, world!")} + // Comment Doc + func unittest() { + // Comment 1 + // Comment 2 + print("Hello, world!")} + // Comment 3` + + lineLimit := 2 + stmtLimit := 2 + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "", input, parser.ParseComments) + + if err != nil { + t.Error("\nActual: ", err, "\n Did not expected error") + } + + r := Run(f, fset, lineLimit, stmtLimit, true) + + if len(r) > 0 { + t.Error("\nActual: ", r, "\nExpected no lint errors") + } +}