10#include "clang/AST/RecursiveASTVisitor.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include "llvm/ADT/BitVector.h"
19class FunctionASTVisitor :
public RecursiveASTVisitor<FunctionASTVisitor> {
20 using Base = RecursiveASTVisitor<FunctionASTVisitor>;
23 explicit FunctionASTVisitor(
bool IgnoreMacros) : IgnoreMacros(IgnoreMacros) {}
25 bool VisitVarDecl(VarDecl *VD) {
28 if (StructNesting == 0 &&
29 !(isa<ParmVarDecl>(VD) || isa<DecompositionDecl>(VD)) &&
30 shouldCountLocation(VD->getBeginLoc()))
34 bool VisitBindingDecl(BindingDecl *BD) {
36 if (StructNesting == 0 && shouldCountLocation(BD->getBeginLoc()))
41 bool TraverseStmt(Stmt *Node) {
43 return Base::TraverseStmt(Node);
45 if (TrackedParent.back() && !isa<CompoundStmt>(Node) &&
46 shouldCountLocation(Node->getBeginLoc()))
49 switch (Node->getStmtClass()) {
50 case Stmt::IfStmtClass:
51 case Stmt::WhileStmtClass:
52 case Stmt::DoStmtClass:
53 case Stmt::CXXForRangeStmtClass:
54 case Stmt::ForStmtClass:
55 case Stmt::SwitchStmtClass:
56 if (shouldCountLocation(Node->getBeginLoc()))
59 case Stmt::CompoundStmtClass:
60 TrackedParent.push_back(
true);
63 TrackedParent.push_back(
false);
67 Base::TraverseStmt(Node);
69 TrackedParent.pop_back();
74 bool TraverseCompoundStmt(CompoundStmt *Node) {
75 const bool CountThisCompound = shouldCountLocation(Node->getBeginLoc());
80 if (CountThisCompound && (CurrentNestingLevel ==
Info.NestingThreshold))
81 Info.NestingThresholders.push_back(Node->getBeginLoc());
83 if (CountThisCompound)
84 ++CurrentNestingLevel;
85 Base::TraverseCompoundStmt(Node);
86 if (CountThisCompound)
87 --CurrentNestingLevel;
92 bool TraverseDecl(Decl *Node) {
93 TrackedParent.push_back(
false);
94 Base::TraverseDecl(Node);
95 TrackedParent.pop_back();
99 bool TraverseLambdaExpr(LambdaExpr *Node) {
101 Base::TraverseLambdaExpr(Node);
106 bool TraverseCXXRecordDecl(CXXRecordDecl *Node) {
108 Base::TraverseCXXRecordDecl(Node);
113 bool TraverseStmtExpr(StmtExpr *SE) {
115 Base::TraverseStmtExpr(SE);
120 bool TraverseConstructorInitializer(CXXCtorInitializer *Init) {
121 if (CountMemberInitAsStmt && shouldCountLocation(Init->getSourceLocation()))
124 Base::TraverseConstructorInitializer(Init);
128 struct FunctionInfo {
130 unsigned Statements = 0;
131 unsigned Branches = 0;
132 unsigned NestingThreshold = 0;
133 unsigned Variables = 0;
134 std::vector<SourceLocation> NestingThresholders;
137 llvm::BitVector TrackedParent;
138 unsigned StructNesting = 0;
139 unsigned CurrentNestingLevel = 0;
140 bool CountMemberInitAsStmt;
141 const bool IgnoreMacros;
144 bool shouldCountLocation(SourceLocation Loc)
const {
145 return !IgnoreMacros || !Loc.isMacroID();
153 LineThreshold(Options.get(
"LineThreshold", DefaultLineThreshold)),
155 Options.get(
"StatementThreshold", DefaultStatementThreshold)),
156 BranchThreshold(Options.get(
"BranchThreshold", DefaultBranchThreshold)),
158 Options.get(
"ParameterThreshold", DefaultParameterThreshold)),
160 Options.get(
"NestingThreshold", DefaultNestingThreshold)),
162 Options.get(
"VariableThreshold", DefaultVariableThreshold)),
163 CountMemberInitAsStmt(
164 Options.get(
"CountMemberInitAsStmt", DefaultCountMemberInitAsStmt)),
165 IgnoreMacros(Options.get(
"IgnoreMacros", DefaultIgnoreMacros)) {}
168 Options.store(Opts,
"LineThreshold", LineThreshold);
169 Options.store(Opts,
"StatementThreshold", StatementThreshold);
170 Options.store(Opts,
"BranchThreshold", BranchThreshold);
171 Options.store(Opts,
"ParameterThreshold", ParameterThreshold);
172 Options.store(Opts,
"NestingThreshold", NestingThreshold);
173 Options.store(Opts,
"VariableThreshold", VariableThreshold);
174 Options.store(Opts,
"CountMemberInitAsStmt", CountMemberInitAsStmt);
175 Options.store(Opts,
"IgnoreMacros", IgnoreMacros);
181 Finder->addMatcher(functionDecl(unless(isInstantiated()),
182 unless(cxxMethodDecl(ofClass(isLambda()))))
188 const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>(
"func");
190 FunctionASTVisitor Visitor(IgnoreMacros);
191 Visitor.Info.NestingThreshold = NestingThreshold.value_or(-1);
192 Visitor.CountMemberInitAsStmt = CountMemberInitAsStmt;
193 Visitor.TraverseDecl(
const_cast<FunctionDecl *
>(Func));
194 auto &FI = Visitor.Info;
196 if (FI.Statements == 0)
200 if (
const Stmt *Body = Func->getBody()) {
201 const SourceManager *SM = Result.SourceManager;
202 if (SM->isWrittenInSameFile(Body->getBeginLoc(), Body->getEndLoc())) {
203 FI.Lines = SM->getSpellingLineNumber(Body->getEndLoc()) -
204 SM->getSpellingLineNumber(Body->getBeginLoc());
208 const unsigned ActualNumberParameters = Func->getNumParams();
210 if ((LineThreshold && FI.Lines > LineThreshold) ||
211 (StatementThreshold && FI.Statements > StatementThreshold) ||
212 (BranchThreshold && FI.Branches > BranchThreshold) ||
213 (ParameterThreshold && ActualNumberParameters > ParameterThreshold) ||
214 !FI.NestingThresholders.empty() ||
215 (VariableThreshold && FI.Variables > VariableThreshold)) {
216 diag(Func->getLocation(),
217 "function %0 exceeds recommended size/complexity thresholds")
221 if (LineThreshold && FI.Lines > LineThreshold) {
222 diag(Func->getLocation(),
223 "%0 lines including whitespace and comments (threshold %1)",
225 << FI.Lines << LineThreshold.value();
228 if (StatementThreshold && FI.Statements > StatementThreshold) {
229 diag(Func->getLocation(),
"%0 statements (threshold %1)",
231 << FI.Statements << StatementThreshold.value();
234 if (BranchThreshold && FI.Branches > BranchThreshold) {
235 diag(Func->getLocation(),
"%0 branches (threshold %1)", DiagnosticIDs::Note)
236 << FI.Branches << BranchThreshold.value();
239 if (ParameterThreshold && ActualNumberParameters > ParameterThreshold) {
240 diag(Func->getLocation(),
"%0 parameters (threshold %1)",
242 << ActualNumberParameters << ParameterThreshold.value();
245 if (NestingThreshold) {
246 for (
const auto &CSPos : FI.NestingThresholders) {
247 diag(CSPos,
"nesting level %0 starts here (threshold %1)",
249 << *NestingThreshold + 1 << *NestingThreshold;
253 if (VariableThreshold && FI.Variables > VariableThreshold) {
254 diag(Func->getLocation(),
"%0 variables (threshold %1)",
256 << FI.Variables << VariableThreshold.value();
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
FunctionSizeCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
@ Info
An information message.
llvm::StringMap< ClangTidyValue > OptionMap