├── Makefile ├── .travis.yml ├── .gitignore ├── go.mod ├── internal ├── complexity_test.go ├── complexity.go ├── statstypes.go ├── body_test.go ├── variables_test.go ├── nesting.go ├── variables.go ├── models.go ├── body.go ├── runner.go └── runner_test.go ├── go.sum ├── cmd └── golongfuncs │ └── main.go ├── README.md └── LICENSE.txt /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | go test ./... 3 | install: 4 | go install ./... 5 | fmt: 6 | goimports -w . 7 | gofmt -w . 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.7.x 5 | - 1.8.x 6 | - 1.9.x 7 | 8 | install: 9 | - go get github.com/stretchr/testify/assert 10 | - make test 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ctags 2 | tags 3 | # 4 | # Vim: 5 | *.sw* 6 | # 7 | # IDEs 8 | .idea/ 9 | *.iml 10 | .vscode/ 11 | 12 | # Random 13 | .DS_Store 14 | TODO* 15 | golongfuncs 16 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tkrajina/golongfuncs 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/go-playground/assert/v2 v2.2.0 7 | github.com/stretchr/testify v1.8.4 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /internal/complexity_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func controlStmts() { 11 | defer fmt.Println("OK") 12 | if 1 == 2 { 13 | for false { 14 | println(1) 15 | } 16 | } 17 | if 2 == 3 { 18 | } else { 19 | println("jok") 20 | } 21 | var a string 22 | switch a { 23 | case "a": 24 | case "b": 25 | case "x": 26 | default: 27 | } 28 | // for, if, else, switch and defer - A Tour of Go 29 | } 30 | 31 | func TestControlFlows(t *testing.T) { 32 | params := CmdParams{ 33 | Types: []FuncMeasurement{MaxNesting}, 34 | IncludeTests: true, 35 | Verbose: true, 36 | } 37 | stats := Do(params, []string{"."}) 38 | assert.NotEmpty(t, stats) 39 | 40 | s, found := findResultForFunc("controlStmts", stats) 41 | if !assert.True(t, found) { 42 | return 43 | } 44 | 45 | v, err := s.Get(Control) 46 | assert.Nil(t, err) 47 | assert.Equal(t, 9.0, v) 48 | } 49 | -------------------------------------------------------------------------------- /internal/complexity.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "go/ast" 5 | "go/token" 6 | ) 7 | 8 | func calculateComplexity(stats *FunctionStats, fun *ast.FuncDecl) { 9 | v := complexityVisitor{} 10 | ast.Walk(&v, fun) 11 | stats.Set(Complexity, float64(v.complexity)) 12 | stats.Set(Control, float64(v.controlFlows)) 13 | } 14 | 15 | type complexityVisitor struct { 16 | complexity int 17 | controlFlows int 18 | } 19 | 20 | func (v *complexityVisitor) Visit(n ast.Node) ast.Visitor { 21 | switch t := n.(type) { 22 | case *ast.FuncDecl, *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.CaseClause, *ast.CommClause: 23 | v.complexity++ 24 | case *ast.BinaryExpr: 25 | if t.Op == token.LAND || t.Op == token.LOR { 26 | v.complexity++ 27 | } 28 | } 29 | 30 | switch n := n.(type) { 31 | case *ast.IfStmt: 32 | v.controlFlows++ 33 | if n.Else != nil { 34 | v.controlFlows++ 35 | } 36 | case *ast.ForStmt, *ast.RangeStmt, *ast.CaseClause, *ast.CommClause, *ast.DeferStmt, *ast.SelectStmt: 37 | v.controlFlows++ 38 | } 39 | return v 40 | } 41 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 4 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 8 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /internal/statstypes.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | type FuncMeasurement string 4 | 5 | const ( 6 | Lines FuncMeasurement = "lines" 7 | TotalLines = "total_lines" 8 | Len = "len" 9 | TotalLen = "total_len" 10 | CommentLines = "comment_lines" 11 | Comments = "comments" 12 | Complexity = "complexity" 13 | MaxNesting = "max_nesting" 14 | TotalNesting = "total_nesting" 15 | InputParams = "in_params" 16 | OutputParams = "out_params" 17 | Variables = "variables" 18 | Assignments = "assignments" 19 | Control = "control" 20 | Todos = "todos" 21 | TodosCaseinsensitive = "todos_case_insensitive" 22 | ) 23 | 24 | var AllTypes = []FuncMeasurement{ 25 | Lines, 26 | TotalLines, 27 | Len, 28 | TotalLen, 29 | CommentLines, 30 | Comments, 31 | Complexity, 32 | MaxNesting, 33 | TotalNesting, 34 | InputParams, 35 | OutputParams, 36 | Variables, 37 | Assignments, 38 | Control, 39 | Todos, 40 | TodosCaseinsensitive, 41 | } 42 | 43 | func isValidBasicType(ty FuncMeasurement) bool { 44 | for _, t := range AllTypes { 45 | if t == ty { 46 | return true 47 | } 48 | } 49 | return false 50 | } 51 | -------------------------------------------------------------------------------- /internal/body_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | // WTF <-- this will not be counted because it's not part of the function definition 11 | 12 | // WTF BUG NOTE 13 | func sampleFunc() { 14 | // TODO[TK] 15 | // todo 16 | // FIXME 17 | } 18 | 19 | func TestCountCommentTags(t *testing.T) { 20 | params := CmdParams{ 21 | Types: []FuncMeasurement{MaxNesting}, 22 | IncludeTests: true, 23 | Verbose: true, 24 | } 25 | stats := Do(params, []string{"."}) 26 | assert.NotEmpty(t, stats) 27 | 28 | s, found := findResultForFunc("sampleFunc", stats) 29 | if !assert.True(t, found) { 30 | return 31 | } 32 | 33 | v, err := s.Get(Todos) 34 | assert.Nil(t, err) 35 | assert.Equal(t, 5.0, v) 36 | } 37 | 38 | type GenericArgs[T int] struct { 39 | Value T 40 | } 41 | 42 | func GenericFunc[T any](t T, arg GenericArgs[int]) string { 43 | if 1 == 2 { 44 | return "" 45 | } 46 | // TODO 47 | return fmt.Sprint("a") 48 | } 49 | 50 | func TestGeneric(t *testing.T) { 51 | t.Parallel() 52 | 53 | params := CmdParams{ 54 | Types: []FuncMeasurement{MaxNesting}, 55 | IncludeTests: true, 56 | Verbose: true, 57 | } 58 | stats := Do(params, []string{"."}) 59 | s, found := findResultForFunc("GenericFunc", stats) 60 | if !assert.True(t, found) { 61 | return 62 | } 63 | 64 | { 65 | v, err := s.Get(Lines) 66 | assert.Nil(t, err) 67 | assert.Equal(t, 4.0, v) 68 | } 69 | { 70 | v, err := s.Get(Control) 71 | assert.Nil(t, err) 72 | assert.Equal(t, 1.0, v, "%#v", s) 73 | } 74 | { 75 | v, err := s.Get(Todos) 76 | assert.Nil(t, err) 77 | assert.Equal(t, 1.0, v) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /internal/variables_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func calcVarsExample(s, a2 string, b2 string) (string, float64) { 10 | a := "jkljkL" 11 | var b string 12 | var z, p int 13 | x, y, _ := 1, 2, 3 14 | a = "aaa" 15 | a += "aaa" 16 | if a = "b"; a != b { 17 | } 18 | println(a, b, x, y, z, p) 19 | return "", 0.0 20 | } 21 | 22 | func calcVarsExampleWithNamesOutput(s2, a2 string, b2 string) (w1, w2 string, w3 int, w4 float64) { 23 | a := "jkljkL" 24 | var b string 25 | var z, p int 26 | x, y, _ := 1, 2, 3 27 | x = 7 28 | y += 13 29 | println(a, b, x, y, z, p) 30 | return "", "", 0, 0.0 31 | } 32 | 33 | func TestCalcVariables(t *testing.T) { 34 | testCalcVariables(t, "calcVarsExample", 3.0, 2.0, 6.0, 9.0) 35 | } 36 | 37 | func TestCalcVariablesWithNamedOut(t *testing.T) { 38 | testCalcVariables(t, "calcVarsExampleWithNamesOutput", 3.0, 4.0, 6.0, 8.0) 39 | } 40 | 41 | func testCalcVariables(t *testing.T, funcName string, in, out, vars, assignments float64) { 42 | params := CmdParams{ 43 | Types: []FuncMeasurement{MaxNesting}, 44 | IncludeTests: true, 45 | } 46 | stats := Do(params, []string{"."}) 47 | assert.NotEmpty(t, stats) 48 | 49 | s, found := findResultForFunc(funcName, stats) 50 | if !assert.True(t, found) { 51 | return 52 | } 53 | 54 | v, err := s.Get(InputParams) 55 | assert.Nil(t, err) 56 | assert.Equal(t, in, v) 57 | 58 | v, err = s.Get(OutputParams) 59 | assert.Nil(t, err) 60 | assert.Equal(t, out, v) 61 | 62 | v, err = s.Get(Variables) 63 | assert.Nil(t, err) 64 | assert.Equal(t, vars, v) 65 | 66 | v, err = s.Get(Assignments) 67 | assert.Nil(t, err) 68 | assert.Equal(t, assignments, v) 69 | } 70 | -------------------------------------------------------------------------------- /internal/nesting.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "go/ast" 5 | "strings" 6 | ) 7 | 8 | func calculateNesting(stats *FunctionStats, offset int, fun *ast.FuncDecl, contents string) { 9 | calculateMaxDepth(stats, fun, offset, contents) 10 | } 11 | 12 | func calculateMaxDepth(stats *FunctionStats, node ast.Node, offset int, contents string) { 13 | v := &blockNestingVisitor{ 14 | offset: offset, 15 | contents: contents, 16 | } 17 | ast.Walk(v, node) 18 | 19 | if v.maxNesting == 0 { 20 | v.maxNesting = 1 21 | } 22 | 23 | stats.Set(MaxNesting, float64(v.maxNesting)) 24 | stats.Set(TotalNesting, float64(v.totalNesting)) 25 | } 26 | 27 | type blockNestingVisitor struct { 28 | blocks []*ast.BlockStmt 29 | maxNesting int 30 | totalNesting int 31 | offset int 32 | contents string 33 | } 34 | 35 | func (v *blockNestingVisitor) Visit(node ast.Node) ast.Visitor { 36 | if v.blocks == nil { 37 | v.blocks = make([]*ast.BlockStmt, 0) 38 | } 39 | if node != nil { 40 | if b, is := node.(*ast.BlockStmt); is { 41 | v.calcMaxNesting(b) 42 | v.calcTotalNesting(b) 43 | } 44 | } 45 | return v 46 | } 47 | 48 | func (v *blockNestingVisitor) calcTotalNesting(b *ast.BlockStmt) { 49 | body := v.contents[int(b.Pos())-v.offset-1 : int(b.End())-v.offset] 50 | body = strings.TrimSpace(strings.Trim(strings.TrimSpace(body), "{}")) 51 | c := countLines(body) 52 | //fmt.Println("+", c, "for body:", body) 53 | //fmt.Println("----------------------------------------------------------------------------------------------------") 54 | v.totalNesting += c 55 | } 56 | 57 | func (v *blockNestingVisitor) calcMaxNesting(b *ast.BlockStmt) { 58 | depth := 1 59 | for _, previous := range v.blocks { 60 | if previous.Pos() < b.Pos() && b.End() < previous.End() { 61 | depth += 1 62 | if depth > v.maxNesting { 63 | v.maxNesting = depth 64 | } 65 | } 66 | } 67 | v.blocks = append(v.blocks, b) 68 | } 69 | -------------------------------------------------------------------------------- /internal/variables.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "go/ast" 5 | "go/token" 6 | ) 7 | 8 | // calcFunc returns the number of input params, output params and variables defined in the function 9 | func calculateVariables(stats *FunctionStats, fun *ast.FuncDecl) { 10 | v := &varCounterVisitor{ 11 | assignIdents: map[token.Pos]bool{}, 12 | defineAssignIdents: map[token.Pos]bool{}, 13 | } 14 | ast.Walk(v, fun) 15 | stats.Set(InputParams, float64(v.inParams)) 16 | stats.Set(OutputParams, float64(v.outParams)) 17 | stats.Set(Variables, float64(v.variables)) 18 | stats.Set(Assignments, float64(v.assignments)) 19 | } 20 | 21 | type varCounterVisitor struct { 22 | inParams, outParams, variables, assignments int 23 | 24 | defineAssignIdents map[token.Pos]bool 25 | assignIdents map[token.Pos]bool 26 | } 27 | 28 | func (v *varCounterVisitor) Visit(node ast.Node) ast.Visitor { 29 | if node == nil { 30 | return v 31 | } 32 | switch t := node.(type) { 33 | case *ast.FuncDecl: 34 | if t.Recv != nil && len(t.Recv.List) > 0 { 35 | v.variables++ 36 | } 37 | case *ast.AssignStmt: 38 | for _, l := range t.Lhs { 39 | if t.Tok == token.DEFINE { 40 | v.defineAssignIdents[l.Pos()] = true 41 | } else { 42 | v.assignIdents[l.Pos()] = true 43 | } 44 | } 45 | case *ast.Ident: 46 | if _, is := v.defineAssignIdents[t.Pos()]; is { 47 | if t.Name != "_" { 48 | //fmt.Printf("Assigned variable %s\n", t.Name) 49 | v.variables++ 50 | v.assignments++ 51 | } 52 | } 53 | if _, is := v.assignIdents[t.Pos()]; is { 54 | if t.Name != "_" { 55 | //fmt.Printf("Assigned variable %s\n", t.Name) 56 | v.assignments++ 57 | } 58 | } 59 | case *ast.ValueSpec: 60 | for _, n := range t.Names { 61 | if n.Name != "_" { 62 | //fmt.Printf("Variable %s\n", n.Name) 63 | v.variables++ 64 | v.assignments++ 65 | } 66 | } 67 | case *ast.FuncType: 68 | for _, p := range t.Params.List { 69 | v.inParams += len(p.Names) 70 | } 71 | if t.Results != nil { 72 | for _, r := range t.Results.List { 73 | if r.Names == nil { 74 | v.outParams++ 75 | } else { 76 | v.outParams += len(r.Names) 77 | } 78 | } 79 | } 80 | } 81 | return v 82 | } 83 | -------------------------------------------------------------------------------- /internal/models.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "math" 8 | "regexp" 9 | ) 10 | 11 | type CmdParams struct { 12 | Types []FuncMeasurement 13 | Threshold float64 14 | MinLines int 15 | Top int 16 | IncludeTests bool 17 | IncludeVendor bool 18 | Ignore *regexp.Regexp 19 | IgnoreFuncs *regexp.Regexp 20 | Verbose bool 21 | } 22 | 23 | func (cp CmdParams) Printf(s string, i ...interface{}) { 24 | if !cp.Verbose { 25 | return 26 | } 27 | fmt.Printf(s, i...) 28 | fmt.Println() 29 | } 30 | 31 | type FunctionStats struct { 32 | Receiver, Name, Location string 33 | stats map[FuncMeasurement]float64 34 | } 35 | 36 | func newFunctionStats(name, location string) *FunctionStats { 37 | return &FunctionStats{ 38 | Name: name, 39 | Location: location, 40 | stats: map[FuncMeasurement]float64{}, 41 | } 42 | } 43 | 44 | func (fs FunctionStats) FuncWithRecv() string { 45 | if fs.Receiver == "" { 46 | return fs.Name + "" 47 | } 48 | return fmt.Sprintf("(%s) %s", fs.Receiver, fs.Name) 49 | } 50 | 51 | func (fs FunctionStats) Get(ty FuncMeasurement) (float64, error) { 52 | if strings.Index(string(ty), "/") > 0 { 53 | parts := strings.Split(string(ty), "/") 54 | if len(parts) != 2 { 55 | return 0, fmt.Errorf("Invalit type %s", ty) 56 | } 57 | a, b := FuncMeasurement(parts[0]), FuncMeasurement(parts[1]) 58 | if !isValidBasicType(a) || !isValidBasicType(b) { 59 | return 0, fmt.Errorf("Invalit type %s", ty) 60 | } 61 | val1, val2 := fs.stats[a], fs.stats[b] 62 | 63 | if val2 == 0 { 64 | return math.NaN(), nil 65 | } 66 | return val1 / val2, nil 67 | } else if !isValidBasicType(ty) { 68 | return 0, fmt.Errorf("Invalit type %s", ty) 69 | } 70 | return fs.stats[ty], nil 71 | } 72 | 73 | func (fs FunctionStats) Set(ty FuncMeasurement, value float64) { 74 | fs.stats[ty] = value 75 | } 76 | 77 | func (fs FunctionStats) Incr(ty FuncMeasurement, value float64) { 78 | fs.stats[ty] += value 79 | } 80 | 81 | type FunctionStatsList struct { 82 | SortType FuncMeasurement 83 | Stats []FunctionStats 84 | } 85 | 86 | func (s FunctionStatsList) Len() int { return len(s.Stats) } 87 | func (s FunctionStatsList) Swap(i, j int) { s.Stats[i], s.Stats[j] = s.Stats[j], s.Stats[i] } 88 | func (s FunctionStatsList) Less(i, j int) bool { 89 | val1, _ := s.Stats[i].Get(s.SortType) 90 | val2, _ := s.Stats[j].Get(s.SortType) 91 | return val1 >= val2 92 | } 93 | -------------------------------------------------------------------------------- /internal/body.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "go/ast" 7 | "os" 8 | "strings" 9 | "unicode" 10 | ) 11 | 12 | func PrintUsage(msg string, params ...interface{}) { 13 | if len(msg) > 0 { 14 | fmt.Fprintf(os.Stderr, msg+"\n\n", params...) 15 | } 16 | flag.Usage() 17 | os.Exit(1) 18 | } 19 | 20 | func ParseTypes(types string) ([]FuncMeasurement, error) { 21 | var fs FunctionStats 22 | 23 | var res []FuncMeasurement 24 | parts := strings.Split(types, ",") 25 | for _, p := range parts { 26 | ty := FuncMeasurement(strings.TrimSpace(p)) 27 | if _, err := fs.Get(ty); err != nil { 28 | return nil, err 29 | } 30 | res = append(res, ty) 31 | } 32 | 33 | return res, nil 34 | } 35 | 36 | func calculateLines(stats *FunctionStats, offset int, fun *ast.FuncDecl, contents string, comments []*ast.CommentGroup, funcDocs string) { 37 | funcBody := contents[int(fun.Pos())-offset-1 : int(fun.End())-offset] 38 | withoutComments := funcBody 39 | onlyComments := "" 40 | stats.Set(Comments, float64(len(comments))) 41 | for i := len(comments) - 1; i >= 0; i-- { 42 | c := comments[i] 43 | if c.Pos() >= fun.Pos() && c.End() <= fun.End() { 44 | withoutComments = withoutComments[0:c.Pos()-fun.Pos()+1] + withoutComments[c.End()-fun.Pos()+1:] 45 | onlyComments = contents[int(c.Pos())-offset:int(c.End())-offset] + "\n" + onlyComments 46 | } 47 | } 48 | 49 | caseSensitive, caseInsensitive := countTodos(funcDocs, onlyComments) 50 | stats.Set(Todos, float64(caseSensitive)) 51 | stats.Set(TodosCaseinsensitive, float64(caseInsensitive)) 52 | stats.Set(Len, float64(len([]rune(withoutComments)))) 53 | stats.Set(TotalLen, float64(len([]rune(funcBody)))) 54 | stats.Set(TotalLines, float64(countLines(funcBody))) 55 | stats.Set(Lines, float64(countLines(withoutComments, "", "}"))) 56 | stats.Set(CommentLines, float64(countLines(onlyComments, "", "//", "/*", "*/"))) 57 | } 58 | 59 | var todoTags = map[string]bool{ 60 | "HACK": true, 61 | "TODO": true, 62 | "NOTE": true, 63 | "FIXME": true, 64 | "ASAP": true, 65 | "ISSUE": true, 66 | "BUG": true, 67 | "WTF": true, 68 | } 69 | 70 | func countTodos(strs ...string) (int, int) { 71 | var caseSensitive, caseInsensitive int 72 | for _, str := range strs { 73 | for _, word := range strings.FieldsFunc(str, func(r rune) bool { 74 | return !unicode.IsLetter(r) 75 | }) { 76 | if _, is := todoTags[word]; is { 77 | caseSensitive++ 78 | } 79 | if _, is := todoTags[strings.ToUpper(word)]; is { 80 | caseInsensitive++ 81 | } 82 | } 83 | } 84 | return caseSensitive, caseInsensitive 85 | } 86 | 87 | func countLines(str string, ignoreLines ...string) int { 88 | ignore := map[string]bool{} 89 | for _, il := range ignoreLines { 90 | ignore[strings.TrimSpace(il)] = true 91 | } 92 | 93 | count := 0 94 | for _, line := range strings.Split(str, "\n") { 95 | line = strings.TrimSpace(line) 96 | if _, ignored := ignore[line]; ignored { 97 | continue 98 | } 99 | count++ 100 | } 101 | 102 | return count 103 | } 104 | -------------------------------------------------------------------------------- /cmd/golongfuncs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "regexp" 8 | "strings" 9 | 10 | "github.com/tkrajina/golongfuncs/internal" 11 | ) 12 | 13 | func main() { 14 | ty := make([]string, len(internal.AllTypes)) 15 | for n := range internal.AllTypes { 16 | ty[n] = string(internal.AllTypes[n]) 17 | } 18 | 19 | var ignoreFilesRegexp, ignoreFuncsRegexp, types string 20 | 21 | args := []string{} 22 | for _, arg := range os.Args { 23 | if strings.HasPrefix(arg, "+") { 24 | types += "," + arg[1:] 25 | } else { 26 | args = append(args, arg) 27 | } 28 | } 29 | types = strings.Trim(types, ",") 30 | os.Args = args 31 | 32 | var params internal.CmdParams 33 | flag.StringVar(&types, "type", types, "Type of stats, valid types are: "+strings.Join(ty, ", ")) 34 | flag.Float64Var(¶ms.Threshold, "threshold", 0, "Min value, functions with value less than this will be ignored") 35 | flag.IntVar(¶ms.MinLines, "min-lines", 10, "Functions shorter than this will be ignored") 36 | flag.IntVar(¶ms.Top, "top", 25, "Show only top n functions") 37 | flag.BoolVar(¶ms.IncludeTests, "include-tests", false, "Include tests") 38 | flag.BoolVar(¶ms.IncludeVendor, "include-vendor", false, "Include vendored files") 39 | flag.StringVar(&ignoreFilesRegexp, "ignore", "", "Regexp for files/directories to ignore") 40 | flag.StringVar(&ignoreFuncsRegexp, "ignore-func", "", "Regexp for functions to ignore") 41 | flag.BoolVar(¶ms.Verbose, "verbose", false, "Verbose") 42 | flag.Parse() 43 | 44 | paths := flag.Args() 45 | 46 | if len(paths) == 0 { 47 | paths = append(paths, "./...") 48 | } 49 | if len(types) == 0 { 50 | types = fmt.Sprintf("%s,%s,%s", internal.Lines, internal.Complexity, internal.MaxNesting) 51 | } 52 | 53 | prepareParams(¶ms, types, ignoreFilesRegexp, ignoreFuncsRegexp) 54 | stats := internal.Do(params, paths) 55 | printStats(params, stats) 56 | } 57 | 58 | func prepareParams(params *internal.CmdParams, types, ignoreFilesRegexp, ignoreFuncsRegexp string) { 59 | var err error 60 | params.Types, err = internal.ParseTypes(types) 61 | if err != nil { 62 | internal.PrintUsage("Invalid type(s) '%s'", types) 63 | } 64 | if len(ignoreFilesRegexp) > 0 { 65 | r, err := regexp.Compile(ignoreFilesRegexp) 66 | if err != nil { 67 | internal.PrintUsage("Invalid ignore regexp '%s'", ignoreFilesRegexp) 68 | } 69 | params.Ignore = r 70 | } 71 | if len(ignoreFuncsRegexp) > 0 { 72 | r, err := regexp.Compile(ignoreFuncsRegexp) 73 | if err != nil { 74 | internal.PrintUsage("Invalid ignore regexp '%s'", ignoreFuncsRegexp) 75 | } 76 | params.IgnoreFuncs = r 77 | } 78 | } 79 | 80 | func printStats(params internal.CmdParams, stats []internal.FunctionStats) { 81 | count := 0 82 | for _, st := range stats { 83 | val, err := st.Get(params.Types[0]) 84 | if err != nil { 85 | internal.PrintUsage("Invalid type %s\n", params.Types[0]) 86 | } 87 | lines, _ := st.Get(internal.Lines) 88 | if val >= params.Threshold && int(lines) >= params.MinLines { 89 | fmt.Printf("%40s %-40s", shortenTo(st.FuncWithRecv(), 40), shortenTo(st.Location, 40)) 90 | printSingleStat(params.Types[0], val) 91 | count += 1 92 | if len(params.Types) > 1 { 93 | for i := 1; i < len(params.Types); i++ { 94 | val, _ := st.Get(params.Types[i]) 95 | printSingleStat(params.Types[i], val) 96 | } 97 | } 98 | fmt.Println() 99 | } 100 | if count >= params.Top { 101 | return 102 | } 103 | } 104 | } 105 | 106 | func shortenTo(str string, l int) string { 107 | if len(str) > l { 108 | return "..." + str[len(str)-l+5:] 109 | } 110 | return str 111 | } 112 | 113 | func printSingleStat(ty internal.FuncMeasurement, val float64) { 114 | format := fmt.Sprintf("%%%ds", len(string(ty))+8) 115 | fmt.Printf(format, fmt.Sprintf("%s=%.1f", ty, val)) 116 | } 117 | -------------------------------------------------------------------------------- /internal/runner.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/parser" 7 | "go/token" 8 | "io/ioutil" 9 | "os" 10 | "path" 11 | "path/filepath" 12 | "sort" 13 | "strings" 14 | ) 15 | 16 | func isDir(filename string) bool { 17 | fi, err := os.Stat(filename) 18 | return err == nil && fi.IsDir() 19 | } 20 | 21 | func Do(params CmdParams, paths []string) []FunctionStats { 22 | var stats []FunctionStats 23 | for _, path := range paths { 24 | if strings.HasSuffix(path, "/...") { 25 | stats = append(stats, analyzeDirRecursively(params, path[:len(path)-3])...) 26 | } else if isDir(path) { 27 | stats = append(stats, analyzeDir(params, path)...) 28 | } else { 29 | stats = append(stats, analyzeFile(params, path)...) 30 | } 31 | } 32 | 33 | l := FunctionStatsList{ 34 | SortType: params.Types[0], 35 | Stats: stats, 36 | } 37 | sort.Sort(l) 38 | 39 | return l.Stats 40 | } 41 | 42 | func analyzeFile(params CmdParams, fname string) []FunctionStats { 43 | stats := []FunctionStats{} 44 | 45 | if !strings.HasSuffix(fname, ".go") { 46 | return stats 47 | } 48 | 49 | if params.Ignore != nil && params.Ignore.MatchString(fname) { 50 | //fmt.Println("Ignored file", fname) 51 | return stats 52 | } 53 | 54 | isTest := strings.HasSuffix(fname, "_test.go") 55 | //fmt.Println(params.IncludeTests, isTest, fname) 56 | if isTest && !params.IncludeTests { 57 | return stats 58 | } 59 | 60 | fset := token.NewFileSet() 61 | f, err := parser.ParseFile(fset, fname, nil, parser.ParseComments) 62 | if err != nil { 63 | fmt.Fprintf(os.Stderr, "Error parsing %s: %s\n", fname, err.Error()) 64 | return stats 65 | } 66 | 67 | //fmt.Println("file=", "pos=", f.Pos()) 68 | v := NewVisitor(params, f, fset, stats) 69 | ast.Walk(v, f) 70 | 71 | return v.stats 72 | } 73 | 74 | func analyzeDir(params CmdParams, dirname string) []FunctionStats { 75 | finfos, err := ioutil.ReadDir(dirname) 76 | if err != nil { 77 | PrintUsage("Error reading %s: %s", dirname, err.Error()) 78 | } 79 | 80 | stats := []FunctionStats{} 81 | for _, fi := range finfos { 82 | stats = append(stats, analyzeFile(params, path.Join(dirname, fi.Name()))...) 83 | } 84 | 85 | return stats 86 | } 87 | 88 | func analyzeDirRecursively(params CmdParams, dirname string) []FunctionStats { 89 | stats := []FunctionStats{} 90 | 91 | err := filepath.Walk(dirname, func(path string, info os.FileInfo, err error) error { 92 | if !params.IncludeVendor { 93 | if strings.Contains(path, "vendor") { // TODO 94 | return err 95 | } 96 | } 97 | if err == nil && !info.IsDir() && strings.HasSuffix(path, ".go") { 98 | stats = append(stats, analyzeFile(params, path)...) 99 | } 100 | return err 101 | }) 102 | if err != nil { 103 | PrintUsage("Error walking through files %s", err.Error()) 104 | } 105 | return stats 106 | } 107 | 108 | type Visitor struct { 109 | file *ast.File 110 | contents string 111 | fset *token.FileSet 112 | offset int 113 | stats []FunctionStats 114 | params CmdParams 115 | } 116 | 117 | func NewVisitor(params CmdParams, file *ast.File, fset *token.FileSet, stats []FunctionStats) *Visitor { 118 | v := Visitor{ 119 | file: file, 120 | fset: fset, 121 | offset: int(file.Pos()), 122 | stats: stats, 123 | params: params, 124 | } 125 | 126 | f := fset.File(file.Pos()) 127 | if f == nil { 128 | panic("No file found for " + f.Name()) 129 | } 130 | 131 | bytes, err := ioutil.ReadFile(f.Name()) 132 | if err != nil { 133 | PrintUsage("Error reading %s: %s", f.Name(), err.Error()) 134 | } 135 | 136 | v.contents = string(bytes) 137 | 138 | return &v 139 | } 140 | 141 | func (v *Visitor) Visit(node ast.Node) ast.Visitor { 142 | if node == nil { 143 | return v 144 | } 145 | switch n := node.(type) { 146 | case *ast.FuncDecl: 147 | fun := n 148 | if v.params.IgnoreFuncs != nil { 149 | if v.params.IgnoreFuncs.MatchString(fun.Name.Name) { 150 | return v 151 | } 152 | } 153 | stats := newFunctionStats(fun.Name.Name, v.fset.Position(fun.Pos()).String()) 154 | v.params.Printf("Visiting %s in %s", fun.Name.Name, stats.Location) 155 | if fun.Recv != nil && len(fun.Recv.List) > 0 { 156 | ty := fun.Recv.List[0].Type 157 | if st, is := ty.(*ast.StarExpr); is { 158 | stats.Receiver = fmt.Sprintf("*%v", st.X) 159 | } else { 160 | stats.Receiver = fmt.Sprintf("%v", ty) 161 | } 162 | } 163 | var functionDocs string 164 | if n.Doc != nil { 165 | functionDocs = n.Doc.Text() 166 | } 167 | calculateLines(stats, v.offset, fun, v.contents, v.file.Comments, functionDocs) 168 | calculateComplexity(stats, fun) 169 | calculateNesting(stats, v.offset, fun, v.contents) 170 | calculateVariables(stats, fun) 171 | v.stats = append(v.stats, *stats) 172 | //fmt.Printf("stats=%d\n", len(v.stats)) 173 | } 174 | return v 175 | } 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/tkrajina/golongfuncs.svg?branch=master)](https://travis-ci.org/tkrajina/golongfuncs) 2 | 3 | # GoLongFuncs 4 | 5 | `golongfuncs` is a tool for searching "long" functions by various measures (and combinations of measures). 6 | 7 | This tool can help you to answer questions line: 8 | 9 | * What are the longest functions with **total complexity** more than *x*? 10 | * What are the longest functions with complexity **per line of code** more than *x*? 11 | * What are functions with the biggest **number of nested blocks**? 12 | * What are functions with the biggest number of **control flow statements**? 13 | * What are the longest functions with **max block nesting** more than *n*? 14 | * What are the functions with the most **variables defined**? 15 | * What are the functions with the most **variables and assignments**? 16 | * etc. 17 | 18 | In other words, it will help you filter long **and complex** functions from those that are just **long**. 19 | 20 | ## Installation 21 | 22 | go install github.com/tkrajina/golongfuncs/... 23 | 24 | ## Measures 25 | 26 | This tool can calculate the following function "length" and "complexity" measures: 27 | 28 | * `lines`: Number of lines **without empty lines, lines ending blocks (containing only `}`), and comments**, 29 | * `total_lines`: Number of lines **including empty lines and comments**, 30 | * `len`: Number of characters in the function (without comments and empty lines). 31 | * `total_len`: Number of characters in the function (with comments and empty lines). 32 | * `comments`: Number of comments. Multiline comments are counted once, 33 | * `comment_lines`: Number of comment lines, 34 | * `complexity`: [Cyclomatic complexity](https://en.wikipedia.org/wiki/Cyclomatic_complexity) (from [gocyclo](https://github.com/fzipp/gocyclo)), 35 | * `max_nesting`: Max nested blocks depth (note, struct initializations are not counted), 36 | * `total_nesting`: Total nesting (in other words, if the code is formatted properly -- every indentation tab is counted once) 37 | * `in_params`: The number of function input parameters 38 | * `out_params`: The number of function output parameters 39 | * `variables`: The number of variables in the scope of the function (without function arguments and function receivers) 40 | * `assignments`: The number of assignments in the function (including variable declarations, `:=`, `=`, `+=`, `-=`...) 41 | * `control`: The number of control flow statements (`if`, `else`, `switch`, `case`, `default`, `select` and `defer`) 42 | * `todos`: The number "TODO" tags found in comments (including function body and the commend preceding the function). Tags are `HACK`, `TODO`, `NOTE`, `FIXME`, `ASAP`, `ISSUE`, `BUG` and `WTF`. 43 | 44 | In addition to those, you can combine measures. For example: 45 | 46 | * `complexity/lines`: Calculates average complexity per line of code. 47 | * `total_nesting/total_lines`: Calculates average nesting (indentation) per line. 48 | * `comment_lines/total_lines`: Calculates lines of functions per line. 49 | * etc. 50 | 51 | To calculate any of those measure for your Golang code: 52 | 53 | $ golongfuncs +variable 54 | $ golongfuncs +total_nesting 55 | $ golongfuncs +lines 56 | $ golongfuncs +lines,complexity 57 | $ golongfuncs +lines,complexity,complexity/lines 58 | 59 | Calling just `golongfuncs` (without arguments) is an alias for `golongfuncs +lines`. 60 | 61 | ## Usage examples 62 | 63 | $ golongfuncs [flags] +[types] [paths...] 64 | 65 | Find longest functions: 66 | 67 | $ golongfuncs 68 | $ golongfuncs 69 | $ golongfuncs /... 70 | 71 | If path is not specified, golongfuncs assumes it is `./...`. 72 | 73 | Show multiple measures: 74 | 75 | $ golongfuncs +lines 76 | $ golongfuncs +lines,in_params 77 | $ golongfuncs +lines,in_params,complexity/lines 78 | $ golongfuncs +lines +in_params +complexity/lines 79 | 80 | If multiple measures are specified, the results are sorted by the first column (in this example `lines`): 81 | 82 | By default the result shows only the top 20 results. Change that with `-top`: 83 | 84 | $ golongfuncs -top 50 85 | 86 | By default, functions shorter than 10 lines are ignored. You can change that with `-min-lines `. 87 | 88 | The 100 most complex functions: 89 | 90 | $ golongfuncs -top 100 +complexity ./... 91 | 92 | The most complex functions longer than 50 lines: 93 | 94 | $ golongfuncs -min-lines 50 +complexity ./... 95 | 96 | Find long functions, but calculate also their complexity, avg complexity and avg nesting: 97 | 98 | $ golongfuncs +lines,complexity,complexity/lines,total_nesting/total_lines . 99 | ExampleVeryLongfunction golongfuncs/runner_test.go:118:1 lines=305.0 complexity=1.0 complexity/lines=0.1 total_nesting/total_lines=1.0 100 | ExampleVeryComplexFunction golongfuncs/runner_test.go:10:1 lines=69.0 complexity=44.0 complexity/lines=0.6 total_nesting/total_lines=6.7 101 | printStats main.go:54:1 lines=21.0 complexity=9.0 complexity/lines=0.4 total_nesting/total_lines=2.5 102 | main main.go:12:1 lines=19.0 complexity=3.0 complexity/lines=0.2 total_nesting/total_lines=1.0 103 | TestLines golongfuncs/runner_test.go:476:1 lines=15.0 complexity=2.0 complexity/lines=0.1 total_nesting/total_lines=0.9 104 | NewVisitor golongfuncs/runner.go:94:1 lines=15.0 complexity=3.0 complexity/lines=0.2 total_nesting/total_lines=1.0 105 | Get golongfuncs/models.go:34:1 lines=15.0 complexity=7.0 complexity/lines=0.5 total_nesting/total_lines=1.7 106 | TestNesting golongfuncs/runner_test.go:438:1 lines=15.0 complexity=2.0 complexity/lines=0.1 total_nesting/total_lines=0.9 107 | 108 | You can see that `ExampleVeryLongfunction` is long (305 lines), but average complexity is low (0.1) and avg nesting is 1.0. 109 | Avg nesting 1.0 means that there are **no nested blocks** in this function. If half the lines were in a nested block (for example a big `if <expr> { ...code... }` block of code) the avg nesting would be 1.5. 110 | 111 | The `ExampleVeryComplexFunction` is shorter (69 lines) but with an average complexity (per line of code) of 0.6 and avg nesting 6.7 and that is probably a good hint that the function needs refactoring. 112 | 113 | Find functions longer than 5 lines with avg nesting (per line of code) bigger than 5 and include total lines count and lines count: 114 | 115 | $ golongfuncs +total_nesting/total_lines,total_lines,lines -threshold 5 . 116 | ExampleVeryComplexFunction golongfuncs/runner_test.go:10:1 total_nesting/total_lines=6.7 total_lines=108.0 lines=69.0 117 | 118 | Find functions with longest average line length: 119 | 120 | $ golongfuncs +len/lines ./... 121 | $ golongfuncs +total_len/total_lines ./... 122 | 123 | Tests and vendored files are ignored, use `-include-tests` and `-include-vendor` if you want to measure them. 124 | 125 | Arbitrary files/directories can be ignored with `-ignore ""`. For example, if you want to ignore Golang files containing `_generated.go`: `-ignore "^.*_generated.go$"`. 126 | 127 | Functions can be ignored with `-ignore-func "regexp"`. 128 | 129 | # License 130 | 131 | This tool is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 132 | -------------------------------------------------------------------------------- /internal/runner_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func ExampleVeryComplexFunction() { 11 | if 1 == 2 || 3 == 4 && 2 == 5 { 12 | for i := 1; i < 100; i++ { 13 | if 1 == 2 || 3 == 4 && 2 == 5 { 14 | for i := 1; i < 100; i++ { 15 | if 1 == 2 || 3 == 4 && 2 == 5 { 16 | for i := 1; i < 100; i++ { 17 | if 3 == 5 { 18 | fmt.Println() 19 | } 20 | if 3 == 5 { 21 | fmt.Println() 22 | } 23 | if 3 == 5 { 24 | fmt.Println() 25 | } 26 | if 3 == 5 { 27 | fmt.Println() 28 | } 29 | if 3 == 5 { 30 | fmt.Println() 31 | } 32 | if 3 == 5 { 33 | fmt.Println() 34 | } 35 | if 3 == 5 { 36 | fmt.Println() 37 | } 38 | if 3 == 5 { 39 | fmt.Println() 40 | } 41 | if 3 == 5 { 42 | fmt.Println() 43 | } 44 | if 3 == 5 { 45 | fmt.Println() 46 | } 47 | if 3 == 5 { 48 | fmt.Println() 49 | } 50 | if 3 == 5 { 51 | fmt.Println() 52 | } 53 | if 3 == 5 { 54 | fmt.Println() 55 | } 56 | if 3 == 5 { 57 | fmt.Println() 58 | } 59 | if 3 == 5 { 60 | fmt.Println() 61 | } 62 | if 3 == 5 { 63 | fmt.Println() 64 | } 65 | if 3 == 5 { 66 | fmt.Println() 67 | } 68 | if 3 == 5 { 69 | fmt.Println() 70 | } 71 | if 3 == 5 { 72 | fmt.Println() 73 | } 74 | if 3 == 5 { 75 | fmt.Println() 76 | } 77 | if 3 == 5 { 78 | fmt.Println() 79 | } 80 | if 3 == 5 { 81 | fmt.Println() 82 | } 83 | if 3 == 5 { 84 | fmt.Println() 85 | } 86 | if 3 == 5 { 87 | fmt.Println() 88 | } 89 | if 3 == 5 { 90 | fmt.Println() 91 | } 92 | if 3 == 5 { 93 | fmt.Println() 94 | } 95 | if 3 == 5 { 96 | fmt.Println() 97 | } 98 | if 3 == 5 { 99 | fmt.Println() 100 | } 101 | if 3 == 5 { 102 | fmt.Println() 103 | } 104 | if 3 == 5 { 105 | fmt.Println() 106 | } 107 | if 3 == 5 { 108 | fmt.Println() 109 | } 110 | } 111 | } 112 | } 113 | } 114 | } 115 | } 116 | } 117 | 118 | func ExampleVeryLongfunction() { 119 | var i int 120 | i++ 121 | i++ 122 | i++ 123 | i++ 124 | i++ 125 | i++ 126 | i++ 127 | i++ 128 | i++ 129 | i++ 130 | i++ 131 | i++ 132 | i++ 133 | i++ 134 | i++ 135 | i++ 136 | i++ 137 | i++ 138 | i++ 139 | i++ 140 | i++ 141 | i++ 142 | i++ 143 | i++ 144 | i++ 145 | i++ 146 | i++ 147 | i++ 148 | i++ 149 | i++ 150 | i++ 151 | i++ 152 | i++ 153 | i++ 154 | i++ 155 | i++ 156 | i++ 157 | i++ 158 | i++ 159 | i++ 160 | i++ 161 | i++ 162 | i++ 163 | i++ 164 | i++ 165 | i++ 166 | i++ 167 | i++ 168 | i++ 169 | i++ 170 | i++ 171 | i++ 172 | i++ 173 | i++ 174 | i++ 175 | i++ 176 | i++ 177 | i++ 178 | i++ 179 | i++ 180 | i++ 181 | i++ 182 | i++ 183 | i++ 184 | i++ 185 | i++ 186 | i++ 187 | i++ 188 | i++ 189 | i++ 190 | i++ 191 | i++ 192 | i++ 193 | i++ 194 | i++ 195 | i++ 196 | i++ 197 | i++ 198 | i++ 199 | i++ 200 | i++ 201 | i++ 202 | i++ 203 | i++ 204 | i++ 205 | i++ 206 | i++ 207 | i++ 208 | i++ 209 | i++ 210 | i++ 211 | i++ 212 | i++ 213 | i++ 214 | i++ 215 | i++ 216 | i++ 217 | i++ 218 | i++ 219 | i++ 220 | i++ 221 | i++ 222 | i++ 223 | i++ 224 | i++ 225 | i++ 226 | i++ 227 | i++ 228 | i++ 229 | i++ 230 | i++ 231 | i++ 232 | i++ 233 | i++ 234 | i++ 235 | i++ 236 | i++ 237 | i++ 238 | i++ 239 | i++ 240 | i++ 241 | i++ 242 | i++ 243 | i++ 244 | i++ 245 | i++ 246 | i++ 247 | i++ 248 | i++ 249 | i++ 250 | i++ 251 | i++ 252 | i++ 253 | i++ 254 | i++ 255 | i++ 256 | i++ 257 | i++ 258 | i++ 259 | i++ 260 | i++ 261 | i++ 262 | i++ 263 | i++ 264 | i++ 265 | i++ 266 | i++ 267 | i++ 268 | i++ 269 | i++ 270 | i++ 271 | i++ 272 | i++ 273 | i++ 274 | i++ 275 | i++ 276 | i++ 277 | i++ 278 | i++ 279 | i++ 280 | i++ 281 | i++ 282 | i++ 283 | i++ 284 | i++ 285 | i++ 286 | i++ 287 | i++ 288 | i++ 289 | i++ 290 | i++ 291 | i++ 292 | i++ 293 | i++ 294 | i++ 295 | i++ 296 | i++ 297 | i++ 298 | i++ 299 | i++ 300 | i++ 301 | i++ 302 | i++ 303 | i++ 304 | i++ 305 | i++ 306 | i++ 307 | i++ 308 | i++ 309 | i++ 310 | i++ 311 | i++ 312 | i++ 313 | i++ 314 | i++ 315 | i++ 316 | i++ 317 | i++ 318 | i++ 319 | i++ 320 | i++ 321 | i++ 322 | i++ 323 | i++ 324 | i++ 325 | i++ 326 | i++ 327 | i++ 328 | i++ 329 | i++ 330 | i++ 331 | i++ 332 | i++ 333 | i++ 334 | i++ 335 | i++ 336 | i++ 337 | i++ 338 | i++ 339 | i++ 340 | i++ 341 | i++ 342 | i++ 343 | i++ 344 | i++ 345 | i++ 346 | i++ 347 | i++ 348 | i++ 349 | i++ 350 | i++ 351 | i++ 352 | i++ 353 | i++ 354 | i++ 355 | i++ 356 | i++ 357 | i++ 358 | i++ 359 | i++ 360 | i++ 361 | i++ 362 | i++ 363 | i++ 364 | i++ 365 | i++ 366 | i++ 367 | i++ 368 | i++ 369 | i++ 370 | i++ 371 | i++ 372 | i++ 373 | i++ 374 | i++ 375 | i++ 376 | i++ 377 | i++ 378 | i++ 379 | i++ 380 | i++ 381 | i++ 382 | i++ 383 | i++ 384 | i++ 385 | i++ 386 | i++ 387 | i++ 388 | i++ 389 | i++ 390 | i++ 391 | i++ 392 | i++ 393 | i++ 394 | i++ 395 | i++ 396 | i++ 397 | i++ 398 | i++ 399 | i++ 400 | i++ 401 | i++ 402 | i++ 403 | { 404 | i++ 405 | i++ 406 | i++ 407 | i++ 408 | i++ 409 | i++ 410 | i++ 411 | i++ 412 | } 413 | i++ 414 | i++ 415 | i++ 416 | i++ 417 | i++ 418 | i++ 419 | i++ 420 | i++ 421 | i++ 422 | i++ 423 | println(i) 424 | } 425 | 426 | func ExampleFuncWithNesting() { 427 | for i := 0; i < 5; i++ { 428 | { 429 | go func() { 430 | func() { 431 | fmt.Println(1) 432 | }() 433 | }() 434 | } 435 | } 436 | } 437 | 438 | func TestNesting(t *testing.T) { 439 | params := CmdParams{ 440 | Types: []FuncMeasurement{MaxNesting}, 441 | IncludeTests: true, 442 | } 443 | stats := Do(params, []string{"."}) 444 | assert.NotEmpty(t, stats) 445 | 446 | s, found := findResultForFunc("ExampleFuncWithNesting", stats) 447 | if !assert.True(t, found) { 448 | return 449 | } 450 | 451 | val, err := s.Get(MaxNesting) 452 | assert.Nil(t, err) 453 | assert.Equal(t, 5.0, val) 454 | 455 | val, err = s.Get(TotalNesting) 456 | assert.Nil(t, err) 457 | assert.Equal(t, 25.0, val) 458 | } 459 | 460 | func ExampleFuncWithComments() { 461 | println(1) // ... 462 | /* 463 | 1 464 | 2 465 | 3 466 | 4 467 | 5 468 | */ 469 | println(2) 470 | var err error 471 | if err != nil { 472 | panic(err) 473 | } 474 | } 475 | 476 | func TestLines(t *testing.T) { 477 | params := CmdParams{ 478 | Types: []FuncMeasurement{MaxNesting}, 479 | IncludeTests: true, 480 | } 481 | stats := Do(params, []string{"."}) 482 | assert.NotEmpty(t, stats) 483 | 484 | s, found := findResultForFunc("ExampleFuncWithComments", stats) 485 | if !assert.True(t, found) { 486 | return 487 | } 488 | 489 | val, err := s.Get(Lines) 490 | assert.Nil(t, err) 491 | assert.Equal(t, 6.0, val) 492 | 493 | val, err = s.Get(TotalLines) 494 | assert.Nil(t, err) 495 | assert.Equal(t, 16.0, val) 496 | } 497 | 498 | func TestDividedBy(t *testing.T) { 499 | params := CmdParams{ 500 | Types: []FuncMeasurement{MaxNesting}, 501 | IncludeTests: true, 502 | } 503 | s, found := findResultForFunc("ExampleFuncWithComments", Do(params, []string{"."})) 504 | if !assert.True(t, found) { 505 | return 506 | } 507 | 508 | val1, err := s.Get(Complexity) 509 | assert.Nil(t, err) 510 | 511 | val2, err := s.Get(Lines) 512 | assert.Nil(t, err) 513 | 514 | val3, err := s.Get(FuncMeasurement(fmt.Sprintf("%s/%s", Complexity, Lines))) 515 | assert.Nil(t, err) 516 | 517 | assert.Equal(t, val3, val1/val2) 518 | } 519 | 520 | func findResultForFunc(funcname string, stats []FunctionStats) (*FunctionStats, bool) { 521 | for _, s := range stats { 522 | if s.Name == funcname { 523 | return &s, true 524 | } 525 | } 526 | return nil, false 527 | } 528 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [2017-] [Tomo Krajina] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------