xref: /llvm-project/clang-tools-extra/clang-tidy/readability/FunctionSizeCheck.cpp (revision 9108644dbf54e4727728ab08c8b7e21bfcd12e8c)
1 //===--- FunctionSize.cpp - clang-tidy ------------------------------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "FunctionSizeCheck.h"
11 #include "clang/AST/RecursiveASTVisitor.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 
14 using namespace clang::ast_matchers;
15 
16 namespace clang {
17 namespace tidy {
18 namespace readability {
19 
20 class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> {
21   using Base = RecursiveASTVisitor<FunctionASTVisitor>;
22 
23 public:
24   bool TraverseStmt(Stmt *Node) {
25     if (!Node)
26       return Base::TraverseStmt(Node);
27 
28     if (TrackedParent.back() && !isa<CompoundStmt>(Node))
29       ++Info.Statements;
30 
31     switch (Node->getStmtClass()) {
32     case Stmt::IfStmtClass:
33     case Stmt::WhileStmtClass:
34     case Stmt::DoStmtClass:
35     case Stmt::CXXForRangeStmtClass:
36     case Stmt::ForStmtClass:
37     case Stmt::SwitchStmtClass:
38       ++Info.Branches;
39     // fallthrough
40     case Stmt::CompoundStmtClass:
41       TrackedParent.push_back(true);
42       break;
43     default:
44       TrackedParent.push_back(false);
45       break;
46     }
47 
48     Base::TraverseStmt(Node);
49 
50     TrackedParent.pop_back();
51     return true;
52   }
53 
54   bool TraverseDecl(Decl *Node) {
55     TrackedParent.push_back(false);
56     Base::TraverseDecl(Node);
57     TrackedParent.pop_back();
58     return true;
59   }
60 
61   struct FunctionInfo {
62     FunctionInfo() : Lines(0), Statements(0), Branches(0) {}
63     unsigned Lines;
64     unsigned Statements;
65     unsigned Branches;
66   };
67   FunctionInfo Info;
68   std::vector<bool> TrackedParent;
69 };
70 
71 FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
72     : ClangTidyCheck(Name, Context),
73       LineThreshold(Options.get("LineThreshold", -1U)),
74       StatementThreshold(Options.get("StatementThreshold", 800U)),
75       BranchThreshold(Options.get("BranchThreshold", -1U)),
76       ParameterThreshold(Options.get("ParameterThreshold", 6)) {}
77 
78 void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
79   Options.store(Opts, "LineThreshold", LineThreshold);
80   Options.store(Opts, "StatementThreshold", StatementThreshold);
81   Options.store(Opts, "BranchThreshold", BranchThreshold);
82   Options.store(Opts, "ParameterThreshold", ParameterThreshold);
83 }
84 
85 void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) {
86   Finder->addMatcher(functionDecl(unless(isInstantiated())).bind("func"), this);
87 }
88 
89 void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) {
90   const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
91 
92   FunctionASTVisitor Visitor;
93   Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func));
94   auto &FI = Visitor.Info;
95 
96   if (FI.Statements == 0)
97     return;
98 
99   // Count the lines including whitespace and comments. Really simple.
100   if (const Stmt *Body = Func->getBody()) {
101     SourceManager *SM = Result.SourceManager;
102     if (SM->isWrittenInSameFile(Body->getLocStart(), Body->getLocEnd())) {
103       FI.Lines = SM->getSpellingLineNumber(Body->getLocEnd()) -
104                  SM->getSpellingLineNumber(Body->getLocStart());
105     }
106   }
107 
108   unsigned ActualNumberParameters = Func->getNumParams();
109 
110   if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold ||
111       FI.Branches > BranchThreshold ||
112       ActualNumberParameters > ParameterThreshold) {
113     diag(Func->getLocation(),
114          "function %0 exceeds recommended size/complexity thresholds")
115         << Func;
116   }
117 
118   if (FI.Lines > LineThreshold) {
119     diag(Func->getLocation(),
120          "%0 lines including whitespace and comments (threshold %1)",
121          DiagnosticIDs::Note)
122         << FI.Lines << LineThreshold;
123   }
124 
125   if (FI.Statements > StatementThreshold) {
126     diag(Func->getLocation(), "%0 statements (threshold %1)",
127          DiagnosticIDs::Note)
128         << FI.Statements << StatementThreshold;
129   }
130 
131   if (FI.Branches > BranchThreshold) {
132     diag(Func->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note)
133         << FI.Branches << BranchThreshold;
134   }
135 
136   if (ActualNumberParameters > ParameterThreshold) {
137     diag(Func->getLocation(), "%0 parameters (threshold %1)",
138          DiagnosticIDs::Note)
139         << ActualNumberParameters << ParameterThreshold;
140   }
141 }
142 
143 } // namespace readability
144 } // namespace tidy
145 } // namespace clang
146