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