blob: 4a96b19d1513d37854ce8ba8eb2b60dfcd2801a6 [file] [log] [blame]
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +00001//===--- BracesAroundStatementsCheck.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 "BracesAroundStatementsCheck.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/ASTMatchers/ASTMatchers.h"
13#include "clang/Lex/Lexer.h"
14
15using namespace clang::ast_matchers;
16
17namespace clang {
18namespace tidy {
Alexander Kornienko35ddae42014-10-15 10:51:57 +000019namespace readability {
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +000020namespace {
21
22tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM,
23 const ASTContext *Context) {
24 Token Tok;
25 SourceLocation Beginning =
26 Lexer::GetBeginningOfToken(Loc, SM, Context->getLangOpts());
27 const bool Invalid =
28 Lexer::getRawToken(Beginning, Tok, SM, Context->getLangOpts());
29 assert(!Invalid && "Expected a valid token.");
30
31 if (Invalid)
32 return tok::NUM_TOKENS;
33
34 return Tok.getKind();
35}
36
37SourceLocation forwardSkipWhitespaceAndComments(SourceLocation Loc,
38 const SourceManager &SM,
39 const ASTContext *Context) {
40 assert(Loc.isValid());
41 for (;;) {
Alexander Kornienko7aedf332017-05-09 12:41:11 +000042 while (isWhitespace(*SM.getCharacterData(Loc)))
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +000043 Loc = Loc.getLocWithOffset(1);
44
45 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
46 if (TokKind == tok::NUM_TOKENS || TokKind != tok::comment)
47 return Loc;
48
49 // Fast-forward current token.
50 Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
51 }
52}
53
54SourceLocation findEndLocation(SourceLocation LastTokenLoc,
55 const SourceManager &SM,
56 const ASTContext *Context) {
Alexander Kornienkoa0ddf5f2017-05-22 13:58:16 +000057 SourceLocation Loc =
58 Lexer::GetBeginningOfToken(LastTokenLoc, SM, Context->getLangOpts());
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +000059 // Loc points to the beginning of the last (non-comment non-ws) token
60 // before end or ';'.
61 assert(Loc.isValid());
62 bool SkipEndWhitespaceAndComments = true;
63 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
64 if (TokKind == tok::NUM_TOKENS || TokKind == tok::semi ||
Alexander Kornienkoa0ddf5f2017-05-22 13:58:16 +000065 TokKind == tok::r_brace) {
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +000066 // If we are at ";" or "}", we found the last token. We could use as well
67 // `if (isa<NullStmt>(S))`, but it wouldn't work for nested statements.
68 SkipEndWhitespaceAndComments = false;
69 }
70
71 Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
72 // Loc points past the last token before end or after ';'.
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +000073 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());
78 }
79
80 for (;;) {
81 assert(Loc.isValid());
Alexander Kornienko7aedf332017-05-09 12:41:11 +000082 while (isHorizontalWhitespace(*SM.getCharacterData(Loc))) {
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +000083 Loc = Loc.getLocWithOffset(1);
Alexander Kornienko7aedf332017-05-09 12:41:11 +000084 }
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +000085
Alexander Kornienko7aedf332017-05-09 12:41:11 +000086 if (isVerticalWhitespace(*SM.getCharacterData(Loc))) {
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +000087 // EOL, insert brace before.
88 break;
89 }
90 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
91 if (TokKind != tok::comment) {
92 // Non-comment token, insert brace before.
93 break;
94 }
95
96 SourceLocation TokEndLoc =
97 Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
98 SourceRange TokRange(Loc, TokEndLoc);
99 StringRef Comment = Lexer::getSourceText(
100 CharSourceRange::getTokenRange(TokRange), SM, Context->getLangOpts());
101 if (Comment.startswith("/*") && Comment.find('\n') != StringRef::npos) {
102 // Multi-line block comment, insert brace before.
103 break;
104 }
105 // else: Trailing comment, insert brace after the newline.
106
107 // Fast-forward current token.
108 Loc = TokEndLoc;
109 }
110 return Loc;
111}
112
113} // namespace
114
Alexander Kornienkof3050002014-10-13 12:46:22 +0000115BracesAroundStatementsCheck::BracesAroundStatementsCheck(
116 StringRef Name, ClangTidyContext *Context)
117 : ClangTidyCheck(Name, Context),
118 // Always add braces by default.
119 ShortStatementLines(Options.get("ShortStatementLines", 0U)) {}
120
Mandeep Singh Grang7c7ea7d2016-11-08 07:50:19 +0000121void BracesAroundStatementsCheck::storeOptions(
122 ClangTidyOptions::OptionMap &Opts) {
Alexander Kornienkof3050002014-10-13 12:46:22 +0000123 Options.store(Opts, "ShortStatementLines", ShortStatementLines);
124}
125
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000126void BracesAroundStatementsCheck::registerMatchers(MatchFinder *Finder) {
127 Finder->addMatcher(ifStmt().bind("if"), this);
128 Finder->addMatcher(whileStmt().bind("while"), this);
129 Finder->addMatcher(doStmt().bind("do"), this);
130 Finder->addMatcher(forStmt().bind("for"), this);
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +0000131 Finder->addMatcher(cxxForRangeStmt().bind("for-range"), this);
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000132}
133
Mandeep Singh Grang7c7ea7d2016-11-08 07:50:19 +0000134void BracesAroundStatementsCheck::check(
135 const MatchFinder::MatchResult &Result) {
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000136 const SourceManager &SM = *Result.SourceManager;
137 const ASTContext *Context = Result.Context;
138
139 // Get location of closing parenthesis or 'do' to insert opening brace.
140 if (auto S = Result.Nodes.getNodeAs<ForStmt>("for")) {
141 checkStmt(Result, S->getBody(), S->getRParenLoc());
142 } else if (auto S = Result.Nodes.getNodeAs<CXXForRangeStmt>("for-range")) {
143 checkStmt(Result, S->getBody(), S->getRParenLoc());
144 } else if (auto S = Result.Nodes.getNodeAs<DoStmt>("do")) {
145 checkStmt(Result, S->getBody(), S->getDoLoc(), S->getWhileLoc());
146 } else if (auto S = Result.Nodes.getNodeAs<WhileStmt>("while")) {
147 SourceLocation StartLoc = findRParenLoc(S, SM, Context);
148 if (StartLoc.isInvalid())
149 return;
150 checkStmt(Result, S->getBody(), StartLoc);
151 } else if (auto S = Result.Nodes.getNodeAs<IfStmt>("if")) {
152 SourceLocation StartLoc = findRParenLoc(S, SM, Context);
153 if (StartLoc.isInvalid())
154 return;
Samuel Benzaquencfacf8a2015-06-04 16:36:58 +0000155 if (ForceBracesStmts.erase(S))
156 ForceBracesStmts.insert(S->getThen());
Samuel Benzaquen462501e2015-03-31 13:53:03 +0000157 bool BracedIf = checkStmt(Result, S->getThen(), StartLoc, S->getElseLoc());
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000158 const Stmt *Else = S->getElse();
Samuel Benzaquencfacf8a2015-06-04 16:36:58 +0000159 if (Else && BracedIf)
160 ForceBracesStmts.insert(Else);
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000161 if (Else && !isa<IfStmt>(Else)) {
162 // Omit 'else if' statements here, they will be handled directly.
Alexander Kornienko7aedf332017-05-09 12:41:11 +0000163 checkStmt(Result, Else, S->getElseLoc());
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000164 }
165 } else {
166 llvm_unreachable("Invalid match");
167 }
168}
169
170/// Find location of right parenthesis closing condition
171template <typename IfOrWhileStmt>
172SourceLocation
173BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S,
174 const SourceManager &SM,
175 const ASTContext *Context) {
Alexander Kornienkoffc27792015-09-09 17:06:09 +0000176 // Skip macros.
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000177 if (S->getLocStart().isMacroID())
178 return SourceLocation();
179
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000180 SourceLocation CondEndLoc = S->getCond()->getLocEnd();
181 if (const DeclStmt *CondVar = S->getConditionVariableDeclStmt())
182 CondEndLoc = CondVar->getLocEnd();
183
Haojian Wu60c93162016-02-11 09:57:55 +0000184 if (!CondEndLoc.isValid()) {
185 return SourceLocation();
186 }
187
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000188 SourceLocation PastCondEndLoc =
189 Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, Context->getLangOpts());
Alexander Kornienkod8193642015-12-16 13:19:08 +0000190 if (PastCondEndLoc.isInvalid())
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000191 return SourceLocation();
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000192 SourceLocation RParenLoc =
193 forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, Context);
Alexander Kornienkod8193642015-12-16 13:19:08 +0000194 if (RParenLoc.isInvalid())
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000195 return SourceLocation();
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000196 tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, Context);
Alexander Kornienkod8193642015-12-16 13:19:08 +0000197 if (TokKind != tok::r_paren)
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000198 return SourceLocation();
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000199 return RParenLoc;
200}
201
Samuel Benzaquen462501e2015-03-31 13:53:03 +0000202/// Determine if the statement needs braces around it, and add them if it does.
203/// Returns true if braces where added.
204bool BracesAroundStatementsCheck::checkStmt(
205 const MatchFinder::MatchResult &Result, const Stmt *S,
Samuel Benzaquencfacf8a2015-06-04 16:36:58 +0000206 SourceLocation InitialLoc, SourceLocation EndLocHint) {
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000207 // 1) If there's a corresponding "else" or "while", the check inserts "} "
208 // right before that token.
209 // 2) If there's a multi-line block comment starting on the same line after
210 // the location we're inserting the closing brace at, or there's a non-comment
211 // token, the check inserts "\n}" right before that token.
212 // 3) Otherwise the check finds the end of line (possibly after some block or
213 // line comments) and inserts "\n}" right before that EOL.
214 if (!S || isa<CompoundStmt>(S)) {
215 // Already inside braces.
Samuel Benzaquen462501e2015-03-31 13:53:03 +0000216 return false;
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000217 }
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000218
Haojian Wu67f88062016-02-16 10:31:33 +0000219 if (!InitialLoc.isValid())
220 return false;
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000221 const SourceManager &SM = *Result.SourceManager;
222 const ASTContext *Context = Result.Context;
223
Alexander Kornienkoffc27792015-09-09 17:06:09 +0000224 // Treat macros.
225 CharSourceRange FileRange = Lexer::makeFileCharRange(
226 CharSourceRange::getTokenRange(S->getSourceRange()), SM,
227 Context->getLangOpts());
228 if (FileRange.isInvalid())
229 return false;
230
Alexander Kornienko08023c62015-09-30 12:48:42 +0000231 // Convert InitialLoc to file location, if it's on the same macro expansion
232 // level as the start of the statement. We also need file locations for
233 // Lexer::getLocForEndOfToken working properly.
234 InitialLoc = Lexer::makeFileCharRange(
235 CharSourceRange::getCharRange(InitialLoc, S->getLocStart()),
236 SM, Context->getLangOpts())
237 .getBegin();
238 if (InitialLoc.isInvalid())
239 return false;
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000240 SourceLocation StartLoc =
241 Lexer::getLocForEndOfToken(InitialLoc, 0, SM, Context->getLangOpts());
Alexander Kornienko08023c62015-09-30 12:48:42 +0000242
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000243 // StartLoc points at the location of the opening brace to be inserted.
244 SourceLocation EndLoc;
245 std::string ClosingInsertion;
246 if (EndLocHint.isValid()) {
247 EndLoc = EndLocHint;
248 ClosingInsertion = "} ";
249 } else {
Alexander Kornienkoffc27792015-09-09 17:06:09 +0000250 const auto FREnd = FileRange.getEnd().getLocWithOffset(-1);
251 EndLoc = findEndLocation(FREnd, SM, Context);
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000252 ClosingInsertion = "\n}";
253 }
254
255 assert(StartLoc.isValid());
Alexander Kornienkof3050002014-10-13 12:46:22 +0000256 assert(EndLoc.isValid());
257 // Don't require braces for statements spanning less than certain number of
258 // lines.
Samuel Benzaquencfacf8a2015-06-04 16:36:58 +0000259 if (ShortStatementLines && !ForceBracesStmts.erase(S)) {
Alexander Kornienkof3050002014-10-13 12:46:22 +0000260 unsigned StartLine = SM.getSpellingLineNumber(StartLoc);
261 unsigned EndLine = SM.getSpellingLineNumber(EndLoc);
262 if (EndLine - StartLine < ShortStatementLines)
Samuel Benzaquen462501e2015-03-31 13:53:03 +0000263 return false;
Alexander Kornienkof3050002014-10-13 12:46:22 +0000264 }
265
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000266 auto Diag = diag(StartLoc, "statement should be inside braces");
267 Diag << FixItHint::CreateInsertion(StartLoc, " {")
268 << FixItHint::CreateInsertion(EndLoc, ClosingInsertion);
Samuel Benzaquen462501e2015-03-31 13:53:03 +0000269 return true;
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000270}
271
Samuel Benzaquencfacf8a2015-06-04 16:36:58 +0000272void BracesAroundStatementsCheck::onEndOfTranslationUnit() {
273 ForceBracesStmts.clear();
274}
275
Alexander Kornienko35ddae42014-10-15 10:51:57 +0000276} // namespace readability
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000277} // namespace tidy
278} // namespace clang