blob: b3953a95a6ab80373993d8bee74462d6c48b628f [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 {
19namespace {
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 (;;) {
41 while (isWhitespace(*FullSourceLoc(Loc, SM).getCharacterData()))
42 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) {
56 SourceLocation Loc = LastTokenLoc;
57 // Loc points to the beginning of the last (non-comment non-ws) token
58 // before end or ';'.
59 assert(Loc.isValid());
60 bool SkipEndWhitespaceAndComments = true;
61 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
62 if (TokKind == tok::NUM_TOKENS || TokKind == tok::semi ||
63 TokKind == tok::r_brace) {
64 // If we are at ";" or "}", we found the last token. We could use as well
65 // `if (isa<NullStmt>(S))`, but it wouldn't work for nested statements.
66 SkipEndWhitespaceAndComments = false;
67 }
68
69 Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
70 // Loc points past the last token before end or after ';'.
71
72 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());
81 while (isHorizontalWhitespace(*FullSourceLoc(Loc, SM).getCharacterData()))
82 Loc = Loc.getLocWithOffset(1);
83
84 if (isVerticalWhitespace(*FullSourceLoc(Loc, SM).getCharacterData())) {
85 // EOL, insert brace before.
86 break;
87 }
88 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
89 if (TokKind != tok::comment) {
90 // Non-comment token, insert brace before.
91 break;
92 }
93
94 SourceLocation TokEndLoc =
95 Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts());
96 SourceRange TokRange(Loc, TokEndLoc);
97 StringRef Comment = Lexer::getSourceText(
98 CharSourceRange::getTokenRange(TokRange), SM, Context->getLangOpts());
99 if (Comment.startswith("/*") && Comment.find('\n') != StringRef::npos) {
100 // Multi-line block comment, insert brace before.
101 break;
102 }
103 // else: Trailing comment, insert brace after the newline.
104
105 // Fast-forward current token.
106 Loc = TokEndLoc;
107 }
108 return Loc;
109}
110
111} // namespace
112
113void BracesAroundStatementsCheck::registerMatchers(MatchFinder *Finder) {
114 Finder->addMatcher(ifStmt().bind("if"), this);
115 Finder->addMatcher(whileStmt().bind("while"), this);
116 Finder->addMatcher(doStmt().bind("do"), this);
117 Finder->addMatcher(forStmt().bind("for"), this);
118 Finder->addMatcher(forRangeStmt().bind("for-range"), this);
119}
120
121void
122BracesAroundStatementsCheck::check(const MatchFinder::MatchResult &Result) {
123 const SourceManager &SM = *Result.SourceManager;
124 const ASTContext *Context = Result.Context;
125
126 // Get location of closing parenthesis or 'do' to insert opening brace.
127 if (auto S = Result.Nodes.getNodeAs<ForStmt>("for")) {
128 checkStmt(Result, S->getBody(), S->getRParenLoc());
129 } else if (auto S = Result.Nodes.getNodeAs<CXXForRangeStmt>("for-range")) {
130 checkStmt(Result, S->getBody(), S->getRParenLoc());
131 } else if (auto S = Result.Nodes.getNodeAs<DoStmt>("do")) {
132 checkStmt(Result, S->getBody(), S->getDoLoc(), S->getWhileLoc());
133 } else if (auto S = Result.Nodes.getNodeAs<WhileStmt>("while")) {
134 SourceLocation StartLoc = findRParenLoc(S, SM, Context);
135 if (StartLoc.isInvalid())
136 return;
137 checkStmt(Result, S->getBody(), StartLoc);
138 } else if (auto S = Result.Nodes.getNodeAs<IfStmt>("if")) {
139 SourceLocation StartLoc = findRParenLoc(S, SM, Context);
140 if (StartLoc.isInvalid())
141 return;
142 checkStmt(Result, S->getThen(), StartLoc, S->getElseLoc());
143 const Stmt *Else = S->getElse();
144 if (Else && !isa<IfStmt>(Else)) {
145 // Omit 'else if' statements here, they will be handled directly.
146 checkStmt(Result, Else, S->getElseLoc());
147 }
148 } else {
149 llvm_unreachable("Invalid match");
150 }
151}
152
153/// Find location of right parenthesis closing condition
154template <typename IfOrWhileStmt>
155SourceLocation
156BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S,
157 const SourceManager &SM,
158 const ASTContext *Context) {
159 // Skip macros
160 if (S->getLocStart().isMacroID())
161 return SourceLocation();
162
163 static const char *const ErrorMessage =
164 "cannot find location of closing parenthesis ')'";
165 SourceLocation CondEndLoc = S->getCond()->getLocEnd();
166 if (const DeclStmt *CondVar = S->getConditionVariableDeclStmt())
167 CondEndLoc = CondVar->getLocEnd();
168
169 assert(CondEndLoc.isValid());
170 SourceLocation PastCondEndLoc =
171 Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, Context->getLangOpts());
172 if (PastCondEndLoc.isInvalid()) {
173 diag(CondEndLoc, ErrorMessage);
174 return SourceLocation();
175 }
176 SourceLocation RParenLoc =
177 forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, Context);
178 if (RParenLoc.isInvalid()) {
179 diag(PastCondEndLoc, ErrorMessage);
180 return SourceLocation();
181 }
182 tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, Context);
183 if (TokKind != tok::r_paren) {
184 diag(RParenLoc, ErrorMessage);
185 return SourceLocation();
186 }
187 return RParenLoc;
188}
189
190void
191BracesAroundStatementsCheck::checkStmt(const MatchFinder::MatchResult &Result,
192 const Stmt *S, SourceLocation InitialLoc,
193 SourceLocation EndLocHint) {
194 // 1) If there's a corresponding "else" or "while", the check inserts "} "
195 // right before that token.
196 // 2) If there's a multi-line block comment starting on the same line after
197 // the location we're inserting the closing brace at, or there's a non-comment
198 // token, the check inserts "\n}" right before that token.
199 // 3) Otherwise the check finds the end of line (possibly after some block or
200 // line comments) and inserts "\n}" right before that EOL.
201 if (!S || isa<CompoundStmt>(S)) {
202 // Already inside braces.
203 return;
204 }
205 // Skip macros.
206 if (S->getLocStart().isMacroID())
207 return;
208
209 // TODO: Add an option to insert braces if:
210 // * the body doesn't fit on the same line with the control statement
211 // * the body takes more than one line
212 // * always.
213
214 const SourceManager &SM = *Result.SourceManager;
215 const ASTContext *Context = Result.Context;
216
217 // InitialLoc points at the last token before opening brace to be inserted.
218 assert(InitialLoc.isValid());
219 SourceLocation StartLoc =
220 Lexer::getLocForEndOfToken(InitialLoc, 0, SM, Context->getLangOpts());
221 // StartLoc points at the location of the opening brace to be inserted.
222 SourceLocation EndLoc;
223 std::string ClosingInsertion;
224 if (EndLocHint.isValid()) {
225 EndLoc = EndLocHint;
226 ClosingInsertion = "} ";
227 } else {
228 EndLoc = findEndLocation(S->getLocEnd(), SM, Context);
229 ClosingInsertion = "\n}";
230 }
231
232 assert(StartLoc.isValid());
233 auto Diag = diag(StartLoc, "statement should be inside braces");
234 Diag << FixItHint::CreateInsertion(StartLoc, " {")
235 << FixItHint::CreateInsertion(EndLoc, ClosingInsertion);
236}
237
238} // namespace tidy
239} // namespace clang