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
;
17 namespace clang::tidy::readability
{
19 static tok::TokenKind
getTokenKind(SourceLocation Loc
, const SourceManager
&SM
,
20 const ASTContext
*Context
) {
22 SourceLocation Beginning
=
23 Lexer::GetBeginningOfToken(Loc
, SM
, Context
->getLangOpts());
25 Lexer::getRawToken(Beginning
, Tok
, SM
, Context
->getLangOpts());
26 assert(!Invalid
&& "Expected a valid token.");
29 return tok::NUM_TOKENS
;
35 forwardSkipWhitespaceAndComments(SourceLocation Loc
, const SourceManager
&SM
,
36 const ASTContext
*Context
) {
37 assert(Loc
.isValid());
39 while (isWhitespace(*SM
.getCharacterData(Loc
)))
40 Loc
= Loc
.getLocWithOffset(1);
42 tok::TokenKind TokKind
= getTokenKind(Loc
, SM
, Context
);
43 if (TokKind
!= tok::comment
)
46 // Fast-forward current token.
47 Loc
= Lexer::getLocForEndOfToken(Loc
, 0, SM
, Context
->getLangOpts());
51 static SourceLocation
findEndLocation(const Stmt
&S
, const SourceManager
&SM
,
52 const ASTContext
*Context
) {
54 utils::lexer::getUnifiedEndLoc(S
, SM
, Context
->getLangOpts());
58 // Start searching right after S.
59 Loc
= Loc
.getLocWithOffset(1);
62 assert(Loc
.isValid());
63 while (isHorizontalWhitespace(*SM
.getCharacterData(Loc
))) {
64 Loc
= Loc
.getLocWithOffset(1);
67 if (isVerticalWhitespace(*SM
.getCharacterData(Loc
))) {
68 // EOL, insert brace before.
71 tok::TokenKind TokKind
= getTokenKind(Loc
, SM
, Context
);
72 if (TokKind
!= tok::comment
) {
73 // Non-comment token, insert brace before.
77 SourceLocation TokEndLoc
=
78 Lexer::getLocForEndOfToken(Loc
, 0, SM
, Context
->getLangOpts());
79 SourceRange
TokRange(Loc
, TokEndLoc
);
80 StringRef Comment
= Lexer::getSourceText(
81 CharSourceRange::getTokenRange(TokRange
), SM
, Context
->getLangOpts());
82 if (Comment
.startswith("/*") && Comment
.contains('\n')) {
83 // Multi-line block comment, insert brace before.
86 // else: Trailing comment, insert brace after the newline.
88 // Fast-forward current token.
94 BracesAroundStatementsCheck::BracesAroundStatementsCheck(
95 StringRef Name
, ClangTidyContext
*Context
)
96 : ClangTidyCheck(Name
, Context
),
97 // Always add braces by default.
98 ShortStatementLines(Options
.get("ShortStatementLines", 0U)) {}
100 void BracesAroundStatementsCheck::storeOptions(
101 ClangTidyOptions::OptionMap
&Opts
) {
102 Options
.store(Opts
, "ShortStatementLines", ShortStatementLines
);
105 void BracesAroundStatementsCheck::registerMatchers(MatchFinder
*Finder
) {
106 Finder
->addMatcher(ifStmt().bind("if"), this);
107 Finder
->addMatcher(whileStmt().bind("while"), this);
108 Finder
->addMatcher(doStmt().bind("do"), this);
109 Finder
->addMatcher(forStmt().bind("for"), this);
110 Finder
->addMatcher(cxxForRangeStmt().bind("for-range"), this);
113 void BracesAroundStatementsCheck::check(
114 const MatchFinder::MatchResult
&Result
) {
115 const SourceManager
&SM
= *Result
.SourceManager
;
116 const ASTContext
*Context
= Result
.Context
;
118 // Get location of closing parenthesis or 'do' to insert opening brace.
119 if (const auto *S
= Result
.Nodes
.getNodeAs
<ForStmt
>("for")) {
120 checkStmt(Result
, S
->getBody(), S
->getRParenLoc());
121 } else if (const auto *S
=
122 Result
.Nodes
.getNodeAs
<CXXForRangeStmt
>("for-range")) {
123 checkStmt(Result
, S
->getBody(), S
->getRParenLoc());
124 } else if (const auto *S
= Result
.Nodes
.getNodeAs
<DoStmt
>("do")) {
125 checkStmt(Result
, S
->getBody(), S
->getDoLoc(), S
->getWhileLoc());
126 } else if (const auto *S
= Result
.Nodes
.getNodeAs
<WhileStmt
>("while")) {
127 SourceLocation StartLoc
= findRParenLoc(S
, SM
, Context
);
128 if (StartLoc
.isInvalid())
130 checkStmt(Result
, S
->getBody(), StartLoc
);
131 } else if (const auto *S
= Result
.Nodes
.getNodeAs
<IfStmt
>("if")) {
132 // "if consteval" always has braces.
133 if (S
->isConsteval())
136 SourceLocation StartLoc
= findRParenLoc(S
, SM
, Context
);
137 if (StartLoc
.isInvalid())
139 if (ForceBracesStmts
.erase(S
))
140 ForceBracesStmts
.insert(S
->getThen());
141 bool BracedIf
= checkStmt(Result
, S
->getThen(), StartLoc
, S
->getElseLoc());
142 const Stmt
*Else
= S
->getElse();
143 if (Else
&& BracedIf
)
144 ForceBracesStmts
.insert(Else
);
145 if (Else
&& !isa
<IfStmt
>(Else
)) {
146 // Omit 'else if' statements here, they will be handled directly.
147 checkStmt(Result
, Else
, S
->getElseLoc());
150 llvm_unreachable("Invalid match");
154 /// Find location of right parenthesis closing condition.
155 template <typename IfOrWhileStmt
>
157 BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt
*S
,
158 const SourceManager
&SM
,
159 const ASTContext
*Context
) {
161 if (S
->getBeginLoc().isMacroID())
164 SourceLocation CondEndLoc
= S
->getCond()->getEndLoc();
165 if (const DeclStmt
*CondVar
= S
->getConditionVariableDeclStmt())
166 CondEndLoc
= CondVar
->getEndLoc();
168 if (!CondEndLoc
.isValid()) {
172 SourceLocation PastCondEndLoc
=
173 Lexer::getLocForEndOfToken(CondEndLoc
, 0, SM
, Context
->getLangOpts());
174 if (PastCondEndLoc
.isInvalid())
176 SourceLocation RParenLoc
=
177 forwardSkipWhitespaceAndComments(PastCondEndLoc
, SM
, Context
);
178 if (RParenLoc
.isInvalid())
180 tok::TokenKind TokKind
= getTokenKind(RParenLoc
, SM
, Context
);
181 if (TokKind
!= tok::r_paren
)
186 /// Determine if the statement needs braces around it, and add them if it does.
187 /// Returns true if braces where added.
188 bool BracesAroundStatementsCheck::checkStmt(
189 const MatchFinder::MatchResult
&Result
, const Stmt
*S
,
190 SourceLocation StartLoc
, SourceLocation EndLocHint
) {
192 while (const auto *AS
= dyn_cast
<AttributedStmt
>(S
))
193 S
= AS
->getSubStmt();
195 const SourceManager
&SM
= *Result
.SourceManager
;
196 const ASTContext
*Context
= Result
.Context
;
198 // 1) If there's a corresponding "else" or "while", the check inserts "} "
199 // right before that token.
200 // 2) If there's a multi-line block comment starting on the same line after
201 // the location we're inserting the closing brace at, or there's a non-comment
202 // token, the check inserts "\n}" right before that token.
203 // 3) Otherwise the check finds the end of line (possibly after some block or
204 // line comments) and inserts "\n}" right before that EOL.
205 if (!S
|| isa
<CompoundStmt
>(S
)) {
206 // Already inside braces.
210 // When TreeTransform, Stmt in constexpr IfStmt will be transform to NullStmt.
211 // This NullStmt can be detected according to beginning token.
212 const SourceLocation StmtBeginLoc
= S
->getBeginLoc();
213 if (isa
<NullStmt
>(S
) && StmtBeginLoc
.isValid() &&
214 getTokenKind(StmtBeginLoc
, SM
, Context
) == tok::l_brace
)
217 if (StartLoc
.isInvalid())
220 // Convert StartLoc to file location, if it's on the same macro expansion
221 // level as the start of the statement. We also need file locations for
222 // Lexer::getLocForEndOfToken working properly.
223 StartLoc
= Lexer::makeFileCharRange(
224 CharSourceRange::getCharRange(StartLoc
, S
->getBeginLoc()), SM
,
225 Context
->getLangOpts())
227 if (StartLoc
.isInvalid())
230 Lexer::getLocForEndOfToken(StartLoc
, 0, SM
, Context
->getLangOpts());
232 // StartLoc points at the location of the opening brace to be inserted.
233 SourceLocation EndLoc
;
234 std::string ClosingInsertion
;
235 if (EndLocHint
.isValid()) {
237 ClosingInsertion
= "} ";
239 EndLoc
= findEndLocation(*S
, SM
, Context
);
240 ClosingInsertion
= "\n}";
243 assert(StartLoc
.isValid());
245 // Don't require braces for statements spanning less than certain number of
247 if (ShortStatementLines
&& !ForceBracesStmts
.erase(S
)) {
248 unsigned StartLine
= SM
.getSpellingLineNumber(StartLoc
);
249 unsigned EndLine
= SM
.getSpellingLineNumber(EndLoc
);
250 if (EndLine
- StartLine
< ShortStatementLines
)
254 auto Diag
= diag(StartLoc
, "statement should be inside braces");
256 // Change only if StartLoc and EndLoc are on the same macro expansion level.
257 // This will also catch invalid EndLoc.
258 // Example: LLVM_DEBUG( for(...) do_something() );
259 // In this case fix-it cannot be provided as the semicolon which is not
260 // visible here is part of the macro. Adding braces here would require adding
261 // another semicolon.
262 if (Lexer::makeFileCharRange(
263 CharSourceRange::getTokenRange(SourceRange(
264 SM
.getSpellingLoc(StartLoc
), SM
.getSpellingLoc(EndLoc
))),
265 SM
, Context
->getLangOpts())
269 Diag
<< FixItHint::CreateInsertion(StartLoc
, " {")
270 << FixItHint::CreateInsertion(EndLoc
, ClosingInsertion
);
274 void BracesAroundStatementsCheck::onEndOfTranslationUnit() {
275 ForceBracesStmts
.clear();
278 } // namespace clang::tidy::readability