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/BracesAroundStatement.h"
11 #include "../utils/LexerUtils.h"
12 #include "clang/AST/ASTContext.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Lex/Lexer.h"
16 using namespace clang::ast_matchers
;
18 namespace clang::tidy::readability
{
20 static tok::TokenKind
getTokenKind(SourceLocation Loc
, const SourceManager
&SM
,
21 const LangOptions
&LangOpts
) {
23 SourceLocation Beginning
= Lexer::GetBeginningOfToken(Loc
, SM
, LangOpts
);
24 const bool Invalid
= Lexer::getRawToken(Beginning
, Tok
, SM
, LangOpts
);
25 assert(!Invalid
&& "Expected a valid token.");
28 return tok::NUM_TOKENS
;
34 forwardSkipWhitespaceAndComments(SourceLocation Loc
, const SourceManager
&SM
,
35 const LangOptions
&LangOpts
) {
36 assert(Loc
.isValid());
38 while (isWhitespace(*SM
.getCharacterData(Loc
)))
39 Loc
= Loc
.getLocWithOffset(1);
41 tok::TokenKind TokKind
= getTokenKind(Loc
, SM
, LangOpts
);
42 if (TokKind
!= tok::comment
)
45 // Fast-forward current token.
46 Loc
= Lexer::getLocForEndOfToken(Loc
, 0, SM
, LangOpts
);
50 BracesAroundStatementsCheck::BracesAroundStatementsCheck(
51 StringRef Name
, ClangTidyContext
*Context
)
52 : ClangTidyCheck(Name
, Context
),
53 // Always add braces by default.
54 ShortStatementLines(Options
.get("ShortStatementLines", 0U)) {}
56 void BracesAroundStatementsCheck::storeOptions(
57 ClangTidyOptions::OptionMap
&Opts
) {
58 Options
.store(Opts
, "ShortStatementLines", ShortStatementLines
);
61 void BracesAroundStatementsCheck::registerMatchers(MatchFinder
*Finder
) {
62 Finder
->addMatcher(ifStmt().bind("if"), this);
63 Finder
->addMatcher(whileStmt().bind("while"), this);
64 Finder
->addMatcher(doStmt().bind("do"), this);
65 Finder
->addMatcher(forStmt().bind("for"), this);
66 Finder
->addMatcher(cxxForRangeStmt().bind("for-range"), this);
69 void BracesAroundStatementsCheck::check(
70 const MatchFinder::MatchResult
&Result
) {
71 const SourceManager
&SM
= *Result
.SourceManager
;
72 const ASTContext
*Context
= Result
.Context
;
74 // Get location of closing parenthesis or 'do' to insert opening brace.
75 if (const auto *S
= Result
.Nodes
.getNodeAs
<ForStmt
>("for")) {
76 checkStmt(Result
, S
->getBody(), S
->getRParenLoc());
77 } else if (const auto *S
=
78 Result
.Nodes
.getNodeAs
<CXXForRangeStmt
>("for-range")) {
79 checkStmt(Result
, S
->getBody(), S
->getRParenLoc());
80 } else if (const auto *S
= Result
.Nodes
.getNodeAs
<DoStmt
>("do")) {
81 checkStmt(Result
, S
->getBody(), S
->getDoLoc(), S
->getWhileLoc());
82 } else if (const auto *S
= Result
.Nodes
.getNodeAs
<WhileStmt
>("while")) {
83 SourceLocation StartLoc
= findRParenLoc(S
, SM
, Context
->getLangOpts());
84 if (StartLoc
.isInvalid())
86 checkStmt(Result
, S
->getBody(), StartLoc
);
87 } else if (const auto *S
= Result
.Nodes
.getNodeAs
<IfStmt
>("if")) {
88 // "if consteval" always has braces.
92 SourceLocation StartLoc
= findRParenLoc(S
, SM
, Context
->getLangOpts());
93 if (StartLoc
.isInvalid())
95 if (ForceBracesStmts
.erase(S
))
96 ForceBracesStmts
.insert(S
->getThen());
97 bool BracedIf
= checkStmt(Result
, S
->getThen(), StartLoc
, S
->getElseLoc());
98 const Stmt
*Else
= S
->getElse();
100 ForceBracesStmts
.insert(Else
);
101 if (Else
&& !isa
<IfStmt
>(Else
)) {
102 // Omit 'else if' statements here, they will be handled directly.
103 checkStmt(Result
, Else
, S
->getElseLoc());
106 llvm_unreachable("Invalid match");
110 /// Find location of right parenthesis closing condition.
111 template <typename IfOrWhileStmt
>
113 BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt
*S
,
114 const SourceManager
&SM
,
115 const LangOptions
&LangOpts
) {
117 if (S
->getBeginLoc().isMacroID())
120 SourceLocation CondEndLoc
= S
->getCond()->getEndLoc();
121 if (const DeclStmt
*CondVar
= S
->getConditionVariableDeclStmt())
122 CondEndLoc
= CondVar
->getEndLoc();
124 if (!CondEndLoc
.isValid()) {
128 SourceLocation PastCondEndLoc
=
129 Lexer::getLocForEndOfToken(CondEndLoc
, 0, SM
, LangOpts
);
130 if (PastCondEndLoc
.isInvalid())
132 SourceLocation RParenLoc
=
133 forwardSkipWhitespaceAndComments(PastCondEndLoc
, SM
, LangOpts
);
134 if (RParenLoc
.isInvalid())
136 tok::TokenKind TokKind
= getTokenKind(RParenLoc
, SM
, LangOpts
);
137 if (TokKind
!= tok::r_paren
)
142 /// Determine if the statement needs braces around it, and add them if it does.
143 /// Returns true if braces where added.
144 bool BracesAroundStatementsCheck::checkStmt(
145 const MatchFinder::MatchResult
&Result
, const Stmt
*S
,
146 SourceLocation StartLoc
, SourceLocation EndLocHint
) {
147 while (const auto *AS
= dyn_cast
<AttributedStmt
>(S
))
148 S
= AS
->getSubStmt();
150 const auto BraceInsertionHints
= utils::getBraceInsertionsHints(
151 S
, Result
.Context
->getLangOpts(), *Result
.SourceManager
, StartLoc
,
153 if (BraceInsertionHints
) {
154 if (ShortStatementLines
&& !ForceBracesStmts
.erase(S
) &&
155 BraceInsertionHints
.resultingCompoundLineExtent(*Result
.SourceManager
) <
158 auto Diag
= diag(BraceInsertionHints
.DiagnosticPos
,
159 "statement should be inside braces");
160 if (BraceInsertionHints
.offersFixIts())
161 Diag
<< BraceInsertionHints
.openingBraceFixIt()
162 << BraceInsertionHints
.closingBraceFixIt();
167 void BracesAroundStatementsCheck::onEndOfTranslationUnit() {
168 ForceBracesStmts
.clear();
171 } // namespace clang::tidy::readability