[docs] Add LICENSE.txt to the root of the mono-repo
[llvm-project.git] / clang-tools-extra / clang-tidy / readability / BracesAroundStatementsCheck.cpp
blob07e962a07e843f4605a8eef3a9ea005a62864cd4
1 //===--- BracesAroundStatementsCheck.cpp - clang-tidy ---------------------===//
2 //
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
6 //
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 {
18 namespace tidy {
19 namespace readability {
21 static tok::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.");
30 if (Invalid)
31 return tok::NUM_TOKENS;
33 return Tok.getKind();
36 static SourceLocation
37 forwardSkipWhitespaceAndComments(SourceLocation Loc, const SourceManager &SM,
38 const ASTContext *Context) {
39 assert(Loc.isValid());
40 for (;;) {
41 while (isWhitespace(*SM.getCharacterData(Loc)))
42 Loc = Loc.getLocWithOffset(1);
44 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
45 if (TokKind != tok::comment)
46 return Loc;
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) {
55 SourceLocation Loc =
56 utils::lexer::getUnifiedEndLoc(S, SM, Context->getLangOpts());
57 if (!Loc.isValid())
58 return Loc;
60 // Start searching right after S.
61 Loc = Loc.getLocWithOffset(1);
63 for (;;) {
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.
71 break;
73 tok::TokenKind TokKind = getTokenKind(Loc, SM, Context);
74 if (TokKind != tok::comment) {
75 // Non-comment token, insert brace before.
76 break;
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.
86 break;
88 // else: Trailing comment, insert brace after the newline.
90 // Fast-forward current token.
91 Loc = TokEndLoc;
93 return Loc;
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())
131 return;
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())
136 return;
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());
147 } else {
148 llvm_unreachable("Invalid match");
152 /// Find location of right parenthesis closing condition.
153 template <typename IfOrWhileStmt>
154 SourceLocation
155 BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S,
156 const SourceManager &SM,
157 const ASTContext *Context) {
158 // Skip macros.
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();
181 return RParenLoc;
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.
202 return false;
205 if (!InitialLoc.isValid())
206 return false;
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())
216 .getBegin();
217 if (InitialLoc.isInvalid())
218 return false;
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()) {
226 EndLoc = EndLocHint;
227 ClosingInsertion = "} ";
228 } else {
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
236 // lines.
237 if (ShortStatementLines && !ForceBracesStmts.erase(S)) {
238 unsigned StartLine = SM.getSpellingLineNumber(StartLoc);
239 unsigned EndLine = SM.getSpellingLineNumber(EndLoc);
240 if (EndLine - StartLine < ShortStatementLines)
241 return false;
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())
256 .isInvalid())
257 return false;
259 Diag << FixItHint::CreateInsertion(StartLoc, " {")
260 << FixItHint::CreateInsertion(EndLoc, ClosingInsertion);
261 return true;
264 void BracesAroundStatementsCheck::onEndOfTranslationUnit() {
265 ForceBracesStmts.clear();
268 } // namespace readability
269 } // namespace tidy
270 } // namespace clang