blob: 6a95a5f2a6a248f333e7313271663f2130352e78 [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 (;;) {
42 while (isWhitespace(*FullSourceLoc(Loc, SM).getCharacterData()))
43 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) {
57 SourceLocation Loc = LastTokenLoc;
58 // 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 ||
64 TokKind == tok::r_brace) {
65 // 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 ';'.
72
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());
78 }
79
80 for (;;) {
81 assert(Loc.isValid());
82 while (isHorizontalWhitespace(*FullSourceLoc(Loc, SM).getCharacterData()))
83 Loc = Loc.getLocWithOffset(1);
84
85 if (isVerticalWhitespace(*FullSourceLoc(Loc, SM).getCharacterData())) {
86 // 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
120void
121BracesAroundStatementsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
122 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);
130 Finder->addMatcher(forRangeStmt().bind("for-range"), this);
131}
132
133void
134BracesAroundStatementsCheck::check(const MatchFinder::MatchResult &Result) {
135 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 Benzaquen462501e2015-03-31 13:53:03 +0000154 bool BracedIf = checkStmt(Result, S->getThen(), StartLoc, S->getElseLoc());
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000155 const Stmt *Else = S->getElse();
156 if (Else && !isa<IfStmt>(Else)) {
157 // Omit 'else if' statements here, they will be handled directly.
Samuel Benzaquen462501e2015-03-31 13:53:03 +0000158 checkStmt(Result, Else, S->getElseLoc(), SourceLocation(), BracedIf);
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000159 }
160 } else {
161 llvm_unreachable("Invalid match");
162 }
163}
164
165/// Find location of right parenthesis closing condition
166template <typename IfOrWhileStmt>
167SourceLocation
168BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S,
169 const SourceManager &SM,
170 const ASTContext *Context) {
171 // Skip macros
172 if (S->getLocStart().isMacroID())
173 return SourceLocation();
174
175 static const char *const ErrorMessage =
176 "cannot find location of closing parenthesis ')'";
177 SourceLocation CondEndLoc = S->getCond()->getLocEnd();
178 if (const DeclStmt *CondVar = S->getConditionVariableDeclStmt())
179 CondEndLoc = CondVar->getLocEnd();
180
181 assert(CondEndLoc.isValid());
182 SourceLocation PastCondEndLoc =
183 Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, Context->getLangOpts());
184 if (PastCondEndLoc.isInvalid()) {
185 diag(CondEndLoc, ErrorMessage);
186 return SourceLocation();
187 }
188 SourceLocation RParenLoc =
189 forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, Context);
190 if (RParenLoc.isInvalid()) {
191 diag(PastCondEndLoc, ErrorMessage);
192 return SourceLocation();
193 }
194 tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, Context);
195 if (TokKind != tok::r_paren) {
196 diag(RParenLoc, ErrorMessage);
197 return SourceLocation();
198 }
199 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,
206 SourceLocation InitialLoc, SourceLocation EndLocHint, bool ForceBraces) {
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 }
218 // Skip macros.
219 if (S->getLocStart().isMacroID())
Samuel Benzaquen462501e2015-03-31 13:53:03 +0000220 return false;
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000221
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000222 const SourceManager &SM = *Result.SourceManager;
223 const ASTContext *Context = Result.Context;
224
225 // InitialLoc points at the last token before opening brace to be inserted.
226 assert(InitialLoc.isValid());
227 SourceLocation StartLoc =
228 Lexer::getLocForEndOfToken(InitialLoc, 0, SM, Context->getLangOpts());
229 // StartLoc points at the location of the opening brace to be inserted.
230 SourceLocation EndLoc;
231 std::string ClosingInsertion;
232 if (EndLocHint.isValid()) {
233 EndLoc = EndLocHint;
234 ClosingInsertion = "} ";
235 } else {
236 EndLoc = findEndLocation(S->getLocEnd(), SM, Context);
237 ClosingInsertion = "\n}";
238 }
239
240 assert(StartLoc.isValid());
Alexander Kornienkof3050002014-10-13 12:46:22 +0000241 assert(EndLoc.isValid());
242 // Don't require braces for statements spanning less than certain number of
243 // lines.
Samuel Benzaquen462501e2015-03-31 13:53:03 +0000244 if (ShortStatementLines && !ForceBraces) {
Alexander Kornienkof3050002014-10-13 12:46:22 +0000245 unsigned StartLine = SM.getSpellingLineNumber(StartLoc);
246 unsigned EndLine = SM.getSpellingLineNumber(EndLoc);
247 if (EndLine - StartLine < ShortStatementLines)
Samuel Benzaquen462501e2015-03-31 13:53:03 +0000248 return false;
Alexander Kornienkof3050002014-10-13 12:46:22 +0000249 }
250
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000251 auto Diag = diag(StartLoc, "statement should be inside braces");
252 Diag << FixItHint::CreateInsertion(StartLoc, " {")
253 << FixItHint::CreateInsertion(EndLoc, ClosingInsertion);
Samuel Benzaquen462501e2015-03-31 13:53:03 +0000254 return true;
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000255}
256
Alexander Kornienko35ddae42014-10-15 10:51:57 +0000257} // namespace readability
Alexander Kornienko8f7e7f72014-10-02 19:09:56 +0000258} // namespace tidy
259} // namespace clang