1 //===--- BracesAroundStatementsCheck.cpp - clang-tidy ---------------------===//
3 // 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
7 //===----------------------------------------------------------------------===//
9 #include "BracesAroundStatementsCheck.h"
10 #include "../utils/LexerUtils.h"
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
{
21 static tok::TokenKind
getTokenKind(SourceLocation Loc
, const SourceManager
&SM
,
22 const ASTContext
*Context
) {
24 SourceLocation Beginning
=
25 Lexer::GetBeginningOfToken(Loc
, SM
, Context
->getLangOpts());
27 Lexer::getRawToken(Beginning
, Tok
, SM
, Context
->getLangOpts());
28 assert(!Invalid
&& "Expected a valid token.");
31 return tok::NUM_TOKENS
;
37 forwardSkipWhitespaceAndComments(SourceLocation Loc
, const SourceManager
&SM
,
38 const ASTContext
*Context
) {
39 assert(Loc
.isValid());
41 while (isWhitespace(*SM
.getCharacterData(Loc
)))
42 Loc
= Loc
.getLocWithOffset(1);
44 tok::TokenKind TokKind
= getTokenKind(Loc
, SM
, Context
);
45 if (TokKind
!= tok::comment
)
48 // Fast-forward current token.
49 Loc
= Lexer::getLocForEndOfToken(Loc
, 0, SM
, Context
->getLangOpts());
53 static SourceLocation
findEndLocation(const Stmt
&S
, const SourceManager
&SM
,
54 const ASTContext
*Context
) {
56 utils::lexer::getUnifiedEndLoc(S
, SM
, Context
->getLangOpts());
60 // Start searching right after S.
61 Loc
= Loc
.getLocWithOffset(1);
64 assert(Loc
.isValid());
65 while (isHorizontalWhitespace(*SM
.getCharacterData(Loc
))) {
66 Loc
= Loc
.getLocWithOffset(1);
69 if (isVerticalWhitespace(*SM
.getCharacterData(Loc
))) {
70 // EOL, insert brace before.
73 tok::TokenKind TokKind
= getTokenKind(Loc
, SM
, Context
);
74 if (TokKind
!= tok::comment
) {
75 // Non-comment token, insert brace before.
79 SourceLocation TokEndLoc
=
80 Lexer::getLocForEndOfToken(Loc
, 0, SM
, Context
->getLangOpts());
81 SourceRange
TokRange(Loc
, TokEndLoc
);
82 StringRef Comment
= Lexer::getSourceText(
83 CharSourceRange::getTokenRange(TokRange
), SM
, Context
->getLangOpts());
84 if (Comment
.startswith("/*") && Comment
.contains('\n')) {
85 // Multi-line block comment, insert brace before.
88 // else: Trailing comment, insert brace after the newline.
90 // Fast-forward current token.
96 BracesAroundStatementsCheck::BracesAroundStatementsCheck(
97 StringRef Name
, ClangTidyContext
*Context
)
98 : ClangTidyCheck(Name
, Context
),
99 // Always add braces by default.
100 ShortStatementLines(Options
.get("ShortStatementLines", 0U)) {}
102 void BracesAroundStatementsCheck::storeOptions(
103 ClangTidyOptions::OptionMap
&Opts
) {
104 Options
.store(Opts
, "ShortStatementLines", ShortStatementLines
);
107 void BracesAroundStatementsCheck::registerMatchers(MatchFinder
*Finder
) {
108 Finder
->addMatcher(ifStmt().bind("if"), this);
109 Finder
->addMatcher(whileStmt().bind("while"), this);
110 Finder
->addMatcher(doStmt().bind("do"), this);
111 Finder
->addMatcher(forStmt().bind("for"), this);
112 Finder
->addMatcher(cxxForRangeStmt().bind("for-range"), this);
115 void BracesAroundStatementsCheck::check(
116 const MatchFinder::MatchResult
&Result
) {
117 const SourceManager
&SM
= *Result
.SourceManager
;
118 const ASTContext
*Context
= Result
.Context
;
120 // Get location of closing parenthesis or 'do' to insert opening brace.
121 if (const auto *S
= Result
.Nodes
.getNodeAs
<ForStmt
>("for")) {
122 checkStmt(Result
, S
->getBody(), S
->getRParenLoc());
123 } else if (const auto *S
=
124 Result
.Nodes
.getNodeAs
<CXXForRangeStmt
>("for-range")) {
125 checkStmt(Result
, S
->getBody(), S
->getRParenLoc());
126 } else if (const auto *S
= Result
.Nodes
.getNodeAs
<DoStmt
>("do")) {
127 checkStmt(Result
, S
->getBody(), S
->getDoLoc(), S
->getWhileLoc());
128 } else if (const auto *S
= Result
.Nodes
.getNodeAs
<WhileStmt
>("while")) {
129 SourceLocation StartLoc
= findRParenLoc(S
, SM
, Context
);
130 if (StartLoc
.isInvalid())
132 checkStmt(Result
, S
->getBody(), StartLoc
);
133 } else if (const auto *S
= Result
.Nodes
.getNodeAs
<IfStmt
>("if")) {
134 SourceLocation StartLoc
= findRParenLoc(S
, SM
, Context
);
135 if (StartLoc
.isInvalid())
137 if (ForceBracesStmts
.erase(S
))
138 ForceBracesStmts
.insert(S
->getThen());
139 bool BracedIf
= checkStmt(Result
, S
->getThen(), StartLoc
, S
->getElseLoc());
140 const Stmt
*Else
= S
->getElse();
141 if (Else
&& BracedIf
)
142 ForceBracesStmts
.insert(Else
);
143 if (Else
&& !isa
<IfStmt
>(Else
)) {
144 // Omit 'else if' statements here, they will be handled directly.
145 checkStmt(Result
, Else
, S
->getElseLoc());
148 llvm_unreachable("Invalid match");
152 /// Find location of right parenthesis closing condition.
153 template <typename IfOrWhileStmt
>
155 BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt
*S
,
156 const SourceManager
&SM
,
157 const ASTContext
*Context
) {
159 if (S
->getBeginLoc().isMacroID())
160 return SourceLocation();
162 SourceLocation CondEndLoc
= S
->getCond()->getEndLoc();
163 if (const DeclStmt
*CondVar
= S
->getConditionVariableDeclStmt())
164 CondEndLoc
= CondVar
->getEndLoc();
166 if (!CondEndLoc
.isValid()) {
167 return SourceLocation();
170 SourceLocation PastCondEndLoc
=
171 Lexer::getLocForEndOfToken(CondEndLoc
, 0, SM
, Context
->getLangOpts());
172 if (PastCondEndLoc
.isInvalid())
173 return SourceLocation();
174 SourceLocation RParenLoc
=
175 forwardSkipWhitespaceAndComments(PastCondEndLoc
, SM
, Context
);
176 if (RParenLoc
.isInvalid())
177 return SourceLocation();
178 tok::TokenKind TokKind
= getTokenKind(RParenLoc
, SM
, Context
);
179 if (TokKind
!= tok::r_paren
)
180 return SourceLocation();
184 /// Determine if the statement needs braces around it, and add them if it does.
185 /// Returns true if braces where added.
186 bool BracesAroundStatementsCheck::checkStmt(
187 const MatchFinder::MatchResult
&Result
, const Stmt
*S
,
188 SourceLocation InitialLoc
, SourceLocation EndLocHint
) {
190 while (const auto *AS
= dyn_cast
<AttributedStmt
>(S
))
191 S
= AS
->getSubStmt();
193 // 1) If there's a corresponding "else" or "while", the check inserts "} "
194 // right before that token.
195 // 2) If there's a multi-line block comment starting on the same line after
196 // the location we're inserting the closing brace at, or there's a non-comment
197 // token, the check inserts "\n}" right before that token.
198 // 3) Otherwise the check finds the end of line (possibly after some block or
199 // line comments) and inserts "\n}" right before that EOL.
200 if (!S
|| isa
<CompoundStmt
>(S
)) {
201 // Already inside braces.
205 if (!InitialLoc
.isValid())
207 const SourceManager
&SM
= *Result
.SourceManager
;
208 const ASTContext
*Context
= Result
.Context
;
210 // Convert InitialLoc to file location, if it's on the same macro expansion
211 // level as the start of the statement. We also need file locations for
212 // Lexer::getLocForEndOfToken working properly.
213 InitialLoc
= Lexer::makeFileCharRange(
214 CharSourceRange::getCharRange(InitialLoc
, S
->getBeginLoc()),
215 SM
, Context
->getLangOpts())
217 if (InitialLoc
.isInvalid())
219 SourceLocation StartLoc
=
220 Lexer::getLocForEndOfToken(InitialLoc
, 0, SM
, Context
->getLangOpts());
222 // StartLoc points at the location of the opening brace to be inserted.
223 SourceLocation EndLoc
;
224 std::string ClosingInsertion
;
225 if (EndLocHint
.isValid()) {
227 ClosingInsertion
= "} ";
229 EndLoc
= findEndLocation(*S
, SM
, Context
);
230 ClosingInsertion
= "\n}";
233 assert(StartLoc
.isValid());
235 // Don't require braces for statements spanning less than certain number of
237 if (ShortStatementLines
&& !ForceBracesStmts
.erase(S
)) {
238 unsigned StartLine
= SM
.getSpellingLineNumber(StartLoc
);
239 unsigned EndLine
= SM
.getSpellingLineNumber(EndLoc
);
240 if (EndLine
- StartLine
< ShortStatementLines
)
244 auto Diag
= diag(StartLoc
, "statement should be inside braces");
246 // Change only if StartLoc and EndLoc are on the same macro expansion level.
247 // This will also catch invalid EndLoc.
248 // Example: LLVM_DEBUG( for(...) do_something() );
249 // In this case fix-it cannot be provided as the semicolon which is not
250 // visible here is part of the macro. Adding braces here would require adding
251 // another semicolon.
252 if (Lexer::makeFileCharRange(
253 CharSourceRange::getTokenRange(SourceRange(
254 SM
.getSpellingLoc(StartLoc
), SM
.getSpellingLoc(EndLoc
))),
255 SM
, Context
->getLangOpts())
259 Diag
<< FixItHint::CreateInsertion(StartLoc
, " {")
260 << FixItHint::CreateInsertion(EndLoc
, ClosingInsertion
);
264 void BracesAroundStatementsCheck::onEndOfTranslationUnit() {
265 ForceBracesStmts
.clear();
268 } // namespace readability