11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchers.h"
13 #include "clang/Lex/Lexer.h"
15 using namespace clang::ast_matchers;
19 namespace readability {
22 tok::TokenKind getTokenKind(SourceLocation
Loc,
const SourceManager &
SM,
25 SourceLocation Beginning =
26 Lexer::GetBeginningOfToken(Loc, SM, Context->getLangOpts());
28 Lexer::getRawToken(Beginning, Tok, SM, Context->getLangOpts());
29 assert(!Invalid &&
"Expected a valid token.");
32 return tok::NUM_TOKENS;
37 SourceLocation forwardSkipWhitespaceAndComments(SourceLocation Loc,
38 const SourceManager &SM,
39 const ASTContext *Context) {
40 assert(Loc.isValid());
42 while (isWhitespace(*FullSourceLoc(Loc, SM).getCharacterData()))
43 Loc = Loc.getLocWithOffset(1);
45 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
46 if (TokKind == tok::NUM_TOKENS || TokKind != tok::comment)
50 Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
54 SourceLocation findEndLocation(SourceLocation LastTokenLoc,
55 const SourceManager &SM,
56 const ASTContext *Context) {
57 SourceLocation Loc = LastTokenLoc;
60 assert(Loc.isValid());
61 bool SkipEndWhitespaceAndComments =
true;
62 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
63 if (TokKind == tok::NUM_TOKENS || TokKind == tok::semi ||
64 TokKind == tok::r_brace) {
67 SkipEndWhitespaceAndComments =
false;
70 Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
73 if (SkipEndWhitespaceAndComments) {
74 Loc = forwardSkipWhitespaceAndComments(Loc, SM, Context);
75 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
76 if (TokKind == tok::semi)
77 Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
81 assert(Loc.isValid());
82 while (isHorizontalWhitespace(*FullSourceLoc(Loc, SM).getCharacterData()))
83 Loc = Loc.getLocWithOffset(1);
85 if (isVerticalWhitespace(*FullSourceLoc(Loc, SM).getCharacterData())) {
89 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
90 if (TokKind != tok::comment) {
95 SourceLocation TokEndLoc =
96 Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
97 SourceRange TokRange(Loc, TokEndLoc);
98 StringRef Comment = Lexer::getSourceText(
99 CharSourceRange::getTokenRange(TokRange), SM, Context->getLangOpts());
100 if (Comment.startswith(
"/*") && Comment.find(
'\n') != StringRef::npos) {
114 BracesAroundStatementsCheck::BracesAroundStatementsCheck(
118 ShortStatementLines(Options.get(
"ShortStatementLines", 0U)) {}
122 Options.
store(Opts,
"ShortStatementLines", ShortStatementLines);
126 Finder->addMatcher(ifStmt().bind(
"if"),
this);
127 Finder->addMatcher(whileStmt().bind(
"while"),
this);
128 Finder->addMatcher(doStmt().bind(
"do"),
this);
129 Finder->addMatcher(forStmt().bind(
"for"),
this);
130 Finder->addMatcher(cxxForRangeStmt().bind(
"for-range"),
this);
135 const SourceManager &
SM = *Result.SourceManager;
136 const ASTContext *
Context = Result.Context;
139 if (
auto S = Result.Nodes.getNodeAs<ForStmt>(
"for")) {
140 checkStmt(Result, S->getBody(), S->getRParenLoc());
141 }
else if (
auto S = Result.Nodes.getNodeAs<CXXForRangeStmt>(
"for-range")) {
142 checkStmt(Result, S->getBody(), S->getRParenLoc());
143 }
else if (
auto S = Result.Nodes.getNodeAs<DoStmt>(
"do")) {
144 checkStmt(Result, S->getBody(), S->getDoLoc(), S->getWhileLoc());
145 }
else if (
auto S = Result.Nodes.getNodeAs<WhileStmt>(
"while")) {
146 SourceLocation StartLoc = findRParenLoc(S, SM, Context);
147 if (StartLoc.isInvalid())
149 checkStmt(Result, S->getBody(), StartLoc);
150 }
else if (
auto S = Result.Nodes.getNodeAs<IfStmt>(
"if")) {
151 SourceLocation StartLoc = findRParenLoc(S, SM, Context);
152 if (StartLoc.isInvalid())
154 if (ForceBracesStmts.erase(S))
155 ForceBracesStmts.insert(S->getThen());
156 bool BracedIf = checkStmt(Result, S->getThen(), StartLoc, S->getElseLoc());
157 const Stmt *Else = S->getElse();
158 if (Else && BracedIf)
159 ForceBracesStmts.insert(Else);
160 if (Else && !isa<IfStmt>(Else)) {
162 checkStmt(Result, Else, S->getElseLoc(), SourceLocation());
165 llvm_unreachable(
"Invalid match");
170 template <
typename IfOrWhileStmt>
172 BracesAroundStatementsCheck::findRParenLoc(
const IfOrWhileStmt *S,
173 const SourceManager &
SM,
176 if (S->getLocStart().isMacroID())
177 return SourceLocation();
179 SourceLocation CondEndLoc = S->getCond()->getLocEnd();
180 if (
const DeclStmt *CondVar = S->getConditionVariableDeclStmt())
181 CondEndLoc = CondVar->getLocEnd();
183 assert(CondEndLoc.isValid());
184 SourceLocation PastCondEndLoc =
185 Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, Context->getLangOpts());
186 if (PastCondEndLoc.isInvalid())
187 return SourceLocation();
188 SourceLocation RParenLoc =
189 forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, Context);
190 if (RParenLoc.isInvalid())
191 return SourceLocation();
192 tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, Context);
193 if (TokKind != tok::r_paren)
194 return SourceLocation();
200 bool BracesAroundStatementsCheck::checkStmt(
201 const MatchFinder::MatchResult &
Result,
const Stmt *S,
202 SourceLocation InitialLoc, SourceLocation EndLocHint) {
210 if (!S || isa<CompoundStmt>(S)) {
215 const SourceManager &SM = *Result.SourceManager;
216 const ASTContext *Context = Result.Context;
219 CharSourceRange FileRange = Lexer::makeFileCharRange(
220 CharSourceRange::getTokenRange(S->getSourceRange()), SM,
221 Context->getLangOpts());
222 if (FileRange.isInvalid())
226 assert(InitialLoc.isValid());
230 InitialLoc = Lexer::makeFileCharRange(
231 CharSourceRange::getCharRange(InitialLoc, S->getLocStart()),
232 SM, Context->getLangOpts())
234 if (InitialLoc.isInvalid())
236 SourceLocation StartLoc =
237 Lexer::getLocForEndOfToken(InitialLoc, 0, SM, Context->getLangOpts());
240 SourceLocation EndLoc;
241 std::string ClosingInsertion;
242 if (EndLocHint.isValid()) {
244 ClosingInsertion =
"} ";
246 const auto FREnd = FileRange.getEnd().getLocWithOffset(-1);
247 EndLoc = findEndLocation(FREnd, SM, Context);
248 ClosingInsertion =
"\n}";
251 assert(StartLoc.isValid());
252 assert(EndLoc.isValid());
255 if (ShortStatementLines && !ForceBracesStmts.erase(S)) {
256 unsigned StartLine = SM.getSpellingLineNumber(StartLoc);
257 unsigned EndLine = SM.getSpellingLineNumber(EndLoc);
258 if (EndLine - StartLine < ShortStatementLines)
262 auto Diag =
diag(StartLoc,
"statement should be inside braces");
263 Diag << FixItHint::CreateInsertion(StartLoc,
" {")
264 << FixItHint::CreateInsertion(EndLoc, ClosingInsertion);
269 ForceBracesStmts.clear();
SourceLocation Loc
'#' location in the include directive
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register ASTMatchers with Finder.
std::unique_ptr< ast_matchers::MatchFinder > Finder
Base class for all clang-tidy checks.
void onEndOfTranslationUnit() override
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
std::map< std::string, std::string > OptionMap
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
ClangTidyContext & Context
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.