github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/rule/function-length.go (about) 1 package rule 2 3 import ( 4 "fmt" 5 "go/ast" 6 "reflect" 7 8 "github.com/mgechev/revive/lint" 9 ) 10 11 // FunctionLength lint. 12 type FunctionLength struct{} 13 14 // Apply applies the rule to given file. 15 func (r *FunctionLength) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { 16 maxStmt, maxLines := r.parseArguments(arguments) 17 18 var failures []lint.Failure 19 20 walker := lintFuncLength{ 21 file: file, 22 maxStmt: int(maxStmt), 23 maxLines: int(maxLines), 24 onFailure: func(failure lint.Failure) { 25 failures = append(failures, failure) 26 }, 27 } 28 29 ast.Walk(walker, file.AST) 30 31 return failures 32 } 33 34 // Name returns the rule name. 35 func (r *FunctionLength) Name() string { 36 return "function-length" 37 } 38 39 func (r *FunctionLength) parseArguments(arguments lint.Arguments) (maxStmt int64, maxLines int64) { 40 if len(arguments) != 2 { 41 panic(fmt.Sprintf(`invalid configuration for "function-length" rule, expected 2 arguments but got %d`, len(arguments))) 42 } 43 44 maxStmt, maxStmtOk := arguments[0].(int64) 45 if !maxStmtOk { 46 panic(fmt.Sprintf(`invalid configuration value for max statements in "function-length" rule; need int64 but got %T`, arguments[0])) 47 } 48 if maxStmt < 0 { 49 panic(fmt.Sprintf(`the configuration value for max statements in "function-length" rule cannot be negative, got %d`, maxStmt)) 50 } 51 52 maxLines, maxLinesOk := arguments[1].(int64) 53 if !maxLinesOk { 54 panic(fmt.Sprintf(`invalid configuration value for max lines in "function-length" rule; need int64 but got %T`, arguments[1])) 55 } 56 if maxLines < 0 { 57 panic(fmt.Sprintf(`the configuration value for max statements in "function-length" rule cannot be negative, got %d`, maxLines)) 58 } 59 60 return 61 } 62 63 type lintFuncLength struct { 64 file *lint.File 65 maxStmt int 66 maxLines int 67 onFailure func(lint.Failure) 68 } 69 70 func (w lintFuncLength) Visit(n ast.Node) ast.Visitor { 71 node, ok := n.(*ast.FuncDecl) 72 if !ok { 73 return w 74 } 75 76 body := node.Body 77 if body == nil || len(node.Body.List) == 0 { 78 return nil 79 } 80 81 if w.maxStmt > 0 { 82 stmtCount := w.countStmts(node.Body.List) 83 if stmtCount > w.maxStmt { 84 w.onFailure(lint.Failure{ 85 Confidence: 1, 86 Failure: fmt.Sprintf("maximum number of statements per function exceeded; max %d but got %d", w.maxStmt, stmtCount), 87 Node: node, 88 }) 89 } 90 } 91 92 if w.maxLines > 0 { 93 lineCount := w.countLines(node.Body) 94 if lineCount > w.maxLines { 95 w.onFailure(lint.Failure{ 96 Confidence: 1, 97 Failure: fmt.Sprintf("maximum number of lines per function exceeded; max %d but got %d", w.maxLines, lineCount), 98 Node: node, 99 }) 100 } 101 } 102 103 return nil 104 } 105 106 func (w lintFuncLength) countLines(b *ast.BlockStmt) int { 107 return w.file.ToPosition(b.End()).Line - w.file.ToPosition(b.Pos()).Line - 1 108 } 109 110 func (w lintFuncLength) countStmts(b []ast.Stmt) int { 111 count := 0 112 for _, s := range b { 113 switch stmt := s.(type) { 114 case *ast.BlockStmt: 115 count += w.countStmts(stmt.List) 116 case *ast.IfStmt: 117 count += 1 + w.countBodyListStmts(stmt) 118 if stmt.Else != nil { 119 elseBody, ok := stmt.Else.(*ast.BlockStmt) 120 if ok { 121 count += w.countStmts(elseBody.List) 122 } 123 } 124 case *ast.ForStmt, *ast.RangeStmt, 125 *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt: 126 count += 1 + w.countBodyListStmts(stmt) 127 case *ast.CaseClause: 128 count += w.countStmts(stmt.Body) 129 case *ast.AssignStmt: 130 count += 1 + w.countFuncLitStmts(stmt.Rhs[0]) 131 case *ast.GoStmt: 132 count += 1 + w.countFuncLitStmts(stmt.Call.Fun) 133 case *ast.DeferStmt: 134 count += 1 + w.countFuncLitStmts(stmt.Call.Fun) 135 default: 136 count++ 137 } 138 } 139 140 return count 141 } 142 143 func (w lintFuncLength) countFuncLitStmts(stmt ast.Expr) int { 144 if block, ok := stmt.(*ast.FuncLit); ok { 145 return w.countStmts(block.Body.List) 146 } 147 return 0 148 } 149 150 func (w lintFuncLength) countBodyListStmts(t interface{}) int { 151 i := reflect.ValueOf(t).Elem().FieldByName(`Body`).Elem().FieldByName(`List`).Interface() 152 return w.countStmts(i.([]ast.Stmt)) 153 }