| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "go/ast" |
| 6 | "go/token" |
| 7 | ) |
| 8 | |
| 9 | const ( |
| 10 | maxFuncLines = 120 |
| 11 | maxComplexity = 30 |
| 12 | ) |
| 13 | |
| 14 | func checkComplexity(s *sourceFile) []Finding { |
| 15 | if s.isTest() { |
| 16 | return nil |
| 17 | } |
| 18 | var out []Finding |
| 19 | for _, decl := range s.file.Decls { |
| 20 | fn, ok := decl.(*ast.FuncDecl) |
| 21 | if !ok || fn.Body == nil { |
| 22 | continue |
| 23 | } |
| 24 | line := s.line(fn.Pos()) |
| 25 | if n := s.line(fn.Body.Rbrace) - s.line(fn.Body.Lbrace); n > maxFuncLines { |
| 26 | out = append(out, Finding{s.rel, line, ruleFuncSize, |
| 27 | fmt.Sprintf("%s is %d lines, over the %d-line limit", funcName(fn), n, maxFuncLines), n - maxFuncLines}) |
| 28 | } |
| 29 | if c := cyclomatic(fn.Body); c > maxComplexity { |
| 30 | out = append(out, Finding{s.rel, line, ruleComplexity, |
| 31 | fmt.Sprintf("%s has cyclomatic complexity %d, over %d", funcName(fn), c, maxComplexity), c - maxComplexity}) |
| 32 | } |
| 33 | } |
| 34 | return out |
| 35 | } |
| 36 | |
| 37 | func funcName(fn *ast.FuncDecl) string { |
| 38 | if fn.Recv == nil || len(fn.Recv.List) == 0 { |
| 39 | return fn.Name.Name |
| 40 | } |
| 41 | return receiverName(fn.Recv.List[0].Type) + "." + fn.Name.Name |
| 42 | } |
| 43 | |
| 44 | func receiverName(expr ast.Expr) string { |
| 45 | switch t := expr.(type) { |
| 46 | case *ast.StarExpr: |
| 47 | return receiverName(t.X) |
| 48 | case *ast.IndexExpr: |
| 49 | return receiverName(t.X) |
| 50 | case *ast.IndexListExpr: |
| 51 | return receiverName(t.X) |
| 52 | case *ast.Ident: |
| 53 | return t.Name |
| 54 | } |
| 55 | return "?" |
| 56 | } |
| 57 | |
| 58 | // One plus every branch point, the standard cyclomatic count. A bare `default` |
| 59 | // adds no branch, so only case clauses that actually match are counted. |
| 60 | func cyclomatic(body *ast.BlockStmt) int { |
| 61 | count := 1 |
| 62 | ast.Inspect(body, func(n ast.Node) bool { |
| 63 | switch node := n.(type) { |
| 64 | case *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt: |
| 65 | count++ |
| 66 | case *ast.CaseClause: |
| 67 | if len(node.List) > 0 { |
| 68 | count++ |
| 69 | } |
| 70 | case *ast.CommClause: |
| 71 | if node.Comm != nil { |
| 72 | count++ |
| 73 | } |
| 74 | case *ast.BinaryExpr: |
| 75 | if node.Op == token.LAND || node.Op == token.LOR { |
| 76 | count++ |
| 77 | } |
| 78 | } |
| 79 | return true |
| 80 | }) |
| 81 | return count |
| 82 | } |
| 83 |