blob: 117ef36d78fe802fa8540e446ee03c39cac0a550 [file] [log] [blame]
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +00001//===--- BracesAroundStatementsCheck.cpp - clang-tidy ---------------------===//
2//
Chandler Carruth2946cd72019-01-19 08:50:56 +00003// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +00006//
7//===----------------------------------------------------------------------===//
8
9#include "BracesAroundStatementsCheck.h"
10#include "clang/AST/ASTContext.h"
11#include "clang/ASTMatchers/ASTMatchers.h"
12#include "clang/Lex/Lexer.h"
13
14using namespace clang::ast_matchers;
15
16namespace clang {
17namespace tidy {
Alexander Kornienko35ddae42014-10-15 10:51:57 +000018namespace readability {
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +000019namespace {
20
21tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM,
22 const ASTContext *Context) {
23 Token Tok;
24 SourceLocation Beginning =
25 Lexer::GetBeginningOfToken(Loc, SM, Context->getLangOpts());
26 const bool Invalid =
27 Lexer::getRawToken(Beginning, Tok, SM, Context->getLangOpts());
28 assert(!Invalid && "Expected a valid token.");
29
30 if (Invalid)
31 return tok::NUM_TOKENS;
32
33 return Tok.getKind();
34}
35
36SourceLocation forwardSkipWhitespaceAndComments(SourceLocation Loc,
37 const SourceManager &SM,
38 const ASTContext *Context) {
39 assert(Loc.isValid());
40 for (;;) {
Alexander Kornienko7aedf332017-05-09 12:41:11 +000041 while (isWhitespace(*SM.getCharacterData(Loc)))
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +000042 Loc = Loc.getLocWithOffset(1);
43
44 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
45 if (TokKind == tok::NUM_TOKENS || TokKind != tok::comment)
46 return Loc;
47
48 // Fast-forward current token.
49 Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
50 }
51}
52
53SourceLocation findEndLocation(SourceLocation LastTokenLoc,
54 const SourceManager &SM,
55 const ASTContext *Context) {
Alexander Kornienkoa0ddf5f2017-05-22 13:58:16 +000056 SourceLocation Loc =
57 Lexer::GetBeginningOfToken(LastTokenLoc, SM, Context->getLangOpts());
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +000058 // Loc points to the beginning of the last (non-comment non-ws) token
59 // before end or ';'.
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 ||
Alexander Kornienkoa0ddf5f2017-05-22 13:58:16 +000064 TokKind == tok::r_brace) {
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +000065 // If we are at ";" or "}", we found the last token. We could use as well
66 // `if (isa<NullStmt>(S))`, but it wouldn't work for nested statements.
67 SkipEndWhitespaceAndComments = false;
68 }
69
70 Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
71 // Loc points past the last token before end or after ';'.
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +000072 if (SkipEndWhitespaceAndComments) {
73 Loc = forwardSkipWhitespaceAndComments(Loc, SM, Context);
74 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
75 if (TokKind == tok::semi)
76 Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
77 }
78
79 for (;;) {
80 assert(Loc.isValid());
Alexander Kornienko7aedf332017-05-09 12:41:11 +000081 while (isHorizontalWhitespace(*SM.getCharacterData(Loc))) {
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +000082 Loc = Loc.getLocWithOffset(1);
Alexander Kornienko7aedf332017-05-09 12:41:11 +000083 }
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +000084
Alexander Kornienko7aedf332017-05-09 12:41:11 +000085 if (isVerticalWhitespace(*SM.getCharacterData(Loc))) {
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +000086 // EOL, insert brace before.
87 break;
88 }
89 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
90 if (TokKind != tok::comment) {
91 // Non-comment token, insert brace before.
92 break;
93 }
94
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) {
101 // Multi-line block comment, insert brace before.
102 break;
103 }
104 // else: Trailing comment, insert brace after the newline.
105
106 // Fast-forward current token.
107 Loc = TokEndLoc;
108 }
109 return Loc;
110}
111
112} // namespace
113
Alexander Kornienkof3050002014-10-13 12:46:22 +0000114BracesAroundStatementsCheck::BracesAroundStatementsCheck(
115 StringRef Name, ClangTidyContext *Context)
116 : ClangTidyCheck(Name, Context),
117 // Always add braces by default.
118 ShortStatementLines(Options.get("ShortStatementLines", 0U)) {}
119
Mandeep Singh Grang7c7ea7d2016-11-08 07:50:19 +0000120void BracesAroundStatementsCheck::storeOptions(
121 ClangTidyOptions::OptionMap &Opts) {
Alexander Kornienkof3050002014-10-13 12:46:22 +0000122 Options.store(Opts, "ShortStatementLines", ShortStatementLines);
123}
124
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000125void BracesAroundStatementsCheck::registerMatchers(MatchFinder *Finder) {
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);
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +0000130 Finder->addMatcher(cxxForRangeStmt().bind("for-range"), this);
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000131}
132
Mandeep Singh Grang7c7ea7d2016-11-08 07:50:19 +0000133void BracesAroundStatementsCheck::check(
134 const MatchFinder::MatchResult &Result) {
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000135 const SourceManager &SM = *Result.SourceManager;
136 const ASTContext *Context = Result.Context;
137
138 // Get location of closing parenthesis or 'do' to insert opening brace.
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())
148 return;
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())
153 return;
Samuel Benzaquencfacf8a2015-06-04 16:36:58 +0000154 if (ForceBracesStmts.erase(S))
155 ForceBracesStmts.insert(S->getThen());
Samuel Benzaquen462501e2015-03-31 13:53:03 +0000156 bool BracedIf = checkStmt(Result, S->getThen(), StartLoc, S->getElseLoc());
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000157 const Stmt *Else = S->getElse();
Samuel Benzaquencfacf8a2015-06-04 16:36:58 +0000158 if (Else && BracedIf)
159 ForceBracesStmts.insert(Else);
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000160 if (Else && !isa<IfStmt>(Else)) {
161 // Omit 'else if' statements here, they will be handled directly.
Alexander Kornienko7aedf332017-05-09 12:41:11 +0000162 checkStmt(Result, Else, S->getElseLoc());
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000163 }
164 } else {
165 llvm_unreachable("Invalid match");
166 }
167}
168
Florian Gross7ce3a832017-05-25 11:43:06 +0000169/// Find location of right parenthesis closing condition.
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000170template <typename IfOrWhileStmt>
171SourceLocation
172BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S,
173 const SourceManager &SM,
174 const ASTContext *Context) {
Alexander Kornienkoffc27792015-09-09 17:06:09 +0000175 // Skip macros.
Stephen Kelly43465bf2018-08-09 22:42:26 +0000176 if (S->getBeginLoc().isMacroID())
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000177 return SourceLocation();
178
Stephen Kellyc09197e2018-08-09 22:43:02 +0000179 SourceLocation CondEndLoc = S->getCond()->getEndLoc();
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000180 if (const DeclStmt *CondVar = S->getConditionVariableDeclStmt())
Stephen Kellyc09197e2018-08-09 22:43:02 +0000181 CondEndLoc = CondVar->getEndLoc();
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000182
Haojian Wu60c93162016-02-11 09:57:55 +0000183 if (!CondEndLoc.isValid()) {
184 return SourceLocation();
185 }
186
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000187 SourceLocation PastCondEndLoc =
188 Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, Context->getLangOpts());
Alexander Kornienkod8193642015-12-16 13:19:08 +0000189 if (PastCondEndLoc.isInvalid())
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000190 return SourceLocation();
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000191 SourceLocation RParenLoc =
192 forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, Context);
Alexander Kornienkod8193642015-12-16 13:19:08 +0000193 if (RParenLoc.isInvalid())
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000194 return SourceLocation();
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000195 tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, Context);
Alexander Kornienkod8193642015-12-16 13:19:08 +0000196 if (TokKind != tok::r_paren)
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000197 return SourceLocation();
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000198 return RParenLoc;
199}
200
Samuel Benzaquen462501e2015-03-31 13:53:03 +0000201/// Determine if the statement needs braces around it, and add them if it does.
202/// Returns true if braces where added.
203bool BracesAroundStatementsCheck::checkStmt(
204 const MatchFinder::MatchResult &Result, const Stmt *S,
Samuel Benzaquencfacf8a2015-06-04 16:36:58 +0000205 SourceLocation InitialLoc, SourceLocation EndLocHint) {
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000206 // 1) If there's a corresponding "else" or "while", the check inserts "} "
207 // right before that token.
208 // 2) If there's a multi-line block comment starting on the same line after
209 // the location we're inserting the closing brace at, or there's a non-comment
210 // token, the check inserts "\n}" right before that token.
211 // 3) Otherwise the check finds the end of line (possibly after some block or
212 // line comments) and inserts "\n}" right before that EOL.
213 if (!S || isa<CompoundStmt>(S)) {
214 // Already inside braces.
Samuel Benzaquen462501e2015-03-31 13:53:03 +0000215 return false;
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000216 }
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000217
Haojian Wu67f88062016-02-16 10:31:33 +0000218 if (!InitialLoc.isValid())
219 return false;
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000220 const SourceManager &SM = *Result.SourceManager;
221 const ASTContext *Context = Result.Context;
222
Alexander Kornienkoffc27792015-09-09 17:06:09 +0000223 // Treat macros.
224 CharSourceRange FileRange = Lexer::makeFileCharRange(
225 CharSourceRange::getTokenRange(S->getSourceRange()), SM,
226 Context->getLangOpts());
227 if (FileRange.isInvalid())
228 return false;
229
Alexander Kornienko08023c62015-09-30 12:48:42 +0000230 // Convert InitialLoc to file location, if it's on the same macro expansion
231 // level as the start of the statement. We also need file locations for
232 // Lexer::getLocForEndOfToken working properly.
233 InitialLoc = Lexer::makeFileCharRange(
Stephen Kelly43465bf2018-08-09 22:42:26 +0000234 CharSourceRange::getCharRange(InitialLoc, S->getBeginLoc()),
Alexander Kornienko08023c62015-09-30 12:48:42 +0000235 SM, Context->getLangOpts())
236 .getBegin();
237 if (InitialLoc.isInvalid())
238 return false;
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000239 SourceLocation StartLoc =
240 Lexer::getLocForEndOfToken(InitialLoc, 0, SM, Context->getLangOpts());
Alexander Kornienko08023c62015-09-30 12:48:42 +0000241
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000242 // StartLoc points at the location of the opening brace to be inserted.
243 SourceLocation EndLoc;
244 std::string ClosingInsertion;
245 if (EndLocHint.isValid()) {
246 EndLoc = EndLocHint;
247 ClosingInsertion = "} ";
248 } else {
Alexander Kornienkoffc27792015-09-09 17:06:09 +0000249 const auto FREnd = FileRange.getEnd().getLocWithOffset(-1);
250 EndLoc = findEndLocation(FREnd, SM, Context);
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000251 ClosingInsertion = "\n}";
252 }
253
254 assert(StartLoc.isValid());
Alexander Kornienkof3050002014-10-13 12:46:22 +0000255 assert(EndLoc.isValid());
256 // Don't require braces for statements spanning less than certain number of
257 // lines.
Samuel Benzaquencfacf8a2015-06-04 16:36:58 +0000258 if (ShortStatementLines && !ForceBracesStmts.erase(S)) {
Alexander Kornienkof3050002014-10-13 12:46:22 +0000259 unsigned StartLine = SM.getSpellingLineNumber(StartLoc);
260 unsigned EndLine = SM.getSpellingLineNumber(EndLoc);
261 if (EndLine - StartLine < ShortStatementLines)
Samuel Benzaquen462501e2015-03-31 13:53:03 +0000262 return false;
Alexander Kornienkof3050002014-10-13 12:46:22 +0000263 }
264
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000265 auto Diag = diag(StartLoc, "statement should be inside braces");
266 Diag << FixItHint::CreateInsertion(StartLoc, " {")
267 << FixItHint::CreateInsertion(EndLoc, ClosingInsertion);
Samuel Benzaquen462501e2015-03-31 13:53:03 +0000268 return true;
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000269}
270
Samuel Benzaquencfacf8a2015-06-04 16:36:58 +0000271void BracesAroundStatementsCheck::onEndOfTranslationUnit() {
272 ForceBracesStmts.clear();
273}
274
Alexander Kornienko35ddae42014-10-15 10:51:57 +0000275} // namespace readability
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000276} // namespace tidy
277} // namespace clang