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 || isStringLiteral(TokKind)) {
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);
134 const MatchFinder::MatchResult &
Result) {
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,
174 const ASTContext *Context) {
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 if (!CondEndLoc.isValid()) {
184 return SourceLocation();
187 SourceLocation PastCondEndLoc =
188 Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, Context->getLangOpts());
189 if (PastCondEndLoc.isInvalid())
190 return SourceLocation();
191 SourceLocation RParenLoc =
192 forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, Context);
193 if (RParenLoc.isInvalid())
194 return SourceLocation();
195 tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, Context);
196 if (TokKind != tok::r_paren)
197 return SourceLocation();
203 bool BracesAroundStatementsCheck::checkStmt(
204 const MatchFinder::MatchResult &
Result,
const Stmt *S,
205 SourceLocation InitialLoc, SourceLocation EndLocHint) {
213 if (!S || isa<CompoundStmt>(S)) {
218 if (!InitialLoc.isValid())
220 const SourceManager &SM = *Result.SourceManager;
221 const ASTContext *Context = Result.Context;
224 CharSourceRange FileRange = Lexer::makeFileCharRange(
225 CharSourceRange::getTokenRange(S->getSourceRange()), SM,
226 Context->getLangOpts());
227 if (FileRange.isInvalid())
233 InitialLoc = Lexer::makeFileCharRange(
234 CharSourceRange::getCharRange(InitialLoc, S->getLocStart()),
235 SM, Context->getLangOpts())
237 if (InitialLoc.isInvalid())
239 SourceLocation StartLoc =
240 Lexer::getLocForEndOfToken(InitialLoc, 0, SM, Context->getLangOpts());
243 SourceLocation EndLoc;
244 std::string ClosingInsertion;
245 if (EndLocHint.isValid()) {
247 ClosingInsertion =
"} ";
249 const auto FREnd = FileRange.getEnd().getLocWithOffset(-1);
250 EndLoc = findEndLocation(FREnd, SM, Context);
251 ClosingInsertion =
"\n}";
254 assert(StartLoc.isValid());
255 assert(EndLoc.isValid());
258 if (ShortStatementLines && !ForceBracesStmts.erase(S)) {
259 unsigned StartLine = SM.getSpellingLineNumber(StartLoc);
260 unsigned EndLine = SM.getSpellingLineNumber(EndLoc);
261 if (EndLine - StartLine < ShortStatementLines)
265 auto Diag =
diag(StartLoc,
"statement should be inside braces");
266 Diag << FixItHint::CreateInsertion(StartLoc,
" {")
267 << FixItHint::CreateInsertion(EndLoc, ClosingInsertion);
272 ForceBracesStmts.clear();
SourceLocation Loc
'#' location in the include directive
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers 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.