xref: /llvm-project/clang-tools-extra/clang-tidy/utils/BracesAroundStatement.cpp (revision 7a4e89761a13bfad27a2614ecea5e8698f50336c)
1 //===--- BracesAroundStatement.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 //===----------------------------------------------------------------------===//
8 ///
9 /// \file
10 /// This file provides utilities to put braces around a statement.
11 ///
12 //===----------------------------------------------------------------------===//
13 
14 #include "BracesAroundStatement.h"
15 #include "../utils/LexerUtils.h"
16 #include "LexerUtils.h"
17 #include "clang/AST/ASTContext.h"
18 #include "clang/Basic/CharInfo.h"
19 #include "clang/Basic/LangOptions.h"
20 #include "clang/Lex/Lexer.h"
21 
22 namespace clang::tidy::utils {
23 
operator bool() const24 BraceInsertionHints::operator bool() const { return DiagnosticPos.isValid(); }
25 
offersFixIts() const26 bool BraceInsertionHints::offersFixIts() const {
27   return OpeningBracePos.isValid() && ClosingBracePos.isValid();
28 }
29 
resultingCompoundLineExtent(const SourceManager & SourceMgr) const30 unsigned BraceInsertionHints::resultingCompoundLineExtent(
31     const SourceManager &SourceMgr) const {
32   return SourceMgr.getSpellingLineNumber(ClosingBracePos) -
33          SourceMgr.getSpellingLineNumber(OpeningBracePos);
34 }
35 
openingBraceFixIt() const36 FixItHint BraceInsertionHints::openingBraceFixIt() const {
37   return OpeningBracePos.isValid()
38              ? FixItHint::CreateInsertion(OpeningBracePos, " {")
39              : FixItHint();
40 }
41 
closingBraceFixIt() const42 FixItHint BraceInsertionHints::closingBraceFixIt() const {
43   return ClosingBracePos.isValid()
44              ? FixItHint::CreateInsertion(ClosingBracePos, ClosingBrace)
45              : FixItHint();
46 }
47 
getTokenKind(SourceLocation Loc,const SourceManager & SM,const LangOptions & LangOpts)48 static tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM,
49                                    const LangOptions &LangOpts) {
50   Token Tok;
51   SourceLocation Beginning = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
52   const bool Invalid = Lexer::getRawToken(Beginning, Tok, SM, LangOpts);
53   assert(!Invalid && "Expected a valid token.");
54 
55   if (Invalid)
56     return tok::NUM_TOKENS;
57 
58   return Tok.getKind();
59 }
60 
findEndLocation(const Stmt & S,const SourceManager & SM,const LangOptions & LangOpts)61 static SourceLocation findEndLocation(const Stmt &S, const SourceManager &SM,
62                                       const LangOptions &LangOpts) {
63   SourceLocation Loc = lexer::getUnifiedEndLoc(S, SM, LangOpts);
64   if (!Loc.isValid())
65     return Loc;
66 
67   // Start searching right after S.
68   Loc = Loc.getLocWithOffset(1);
69 
70   for (;;) {
71     assert(Loc.isValid());
72     while (isHorizontalWhitespace(*SM.getCharacterData(Loc))) {
73       Loc = Loc.getLocWithOffset(1);
74     }
75 
76     if (isVerticalWhitespace(*SM.getCharacterData(Loc))) {
77       // EOL, insert brace before.
78       break;
79     }
80     tok::TokenKind TokKind = getTokenKind(Loc, SM, LangOpts);
81     if (TokKind != tok::comment) {
82       // Non-comment token, insert brace before.
83       break;
84     }
85 
86     SourceLocation TokEndLoc = Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts);
87     SourceRange TokRange(Loc, TokEndLoc);
88     StringRef Comment = Lexer::getSourceText(
89         CharSourceRange::getTokenRange(TokRange), SM, LangOpts);
90     if (Comment.starts_with("/*") && Comment.contains('\n')) {
91       // Multi-line block comment, insert brace before.
92       break;
93     }
94     // else: Trailing comment, insert brace after the newline.
95 
96     // Fast-forward current token.
97     Loc = TokEndLoc;
98   }
99   return Loc;
100 }
101 
getBraceInsertionsHints(const Stmt * const S,const LangOptions & LangOpts,const SourceManager & SM,SourceLocation StartLoc,SourceLocation EndLocHint)102 BraceInsertionHints getBraceInsertionsHints(const Stmt *const S,
103                                             const LangOptions &LangOpts,
104                                             const SourceManager &SM,
105                                             SourceLocation StartLoc,
106                                             SourceLocation EndLocHint) {
107   // 1) If there's a corresponding "else" or "while", the check inserts "} "
108   // right before that token.
109   // 2) If there's a multi-line block comment starting on the same line after
110   // the location we're inserting the closing brace at, or there's a non-comment
111   // token, the check inserts "\n}" right before that token.
112   // 3) Otherwise the check finds the end of line (possibly after some block or
113   // line comments) and inserts "\n}" right before that EOL.
114   if (!S || isa<CompoundStmt>(S)) {
115     // Already inside braces.
116     return {};
117   }
118 
119   // When TreeTransform, Stmt in constexpr IfStmt will be transform to NullStmt.
120   // This NullStmt can be detected according to beginning token.
121   const SourceLocation StmtBeginLoc = S->getBeginLoc();
122   if (isa<NullStmt>(S) && StmtBeginLoc.isValid() &&
123       getTokenKind(StmtBeginLoc, SM, LangOpts) == tok::l_brace)
124     return {};
125 
126   if (StartLoc.isInvalid())
127     return {};
128 
129   // Convert StartLoc to file location, if it's on the same macro expansion
130   // level as the start of the statement. We also need file locations for
131   // Lexer::getLocForEndOfToken working properly.
132   StartLoc = Lexer::makeFileCharRange(
133                  CharSourceRange::getCharRange(StartLoc, S->getBeginLoc()), SM,
134                  LangOpts)
135                  .getBegin();
136   if (StartLoc.isInvalid())
137     return {};
138   StartLoc = Lexer::getLocForEndOfToken(StartLoc, 0, SM, LangOpts);
139 
140   // StartLoc points at the location of the opening brace to be inserted.
141   SourceLocation EndLoc;
142   std::string ClosingInsertion;
143   if (EndLocHint.isValid()) {
144     EndLoc = EndLocHint;
145     ClosingInsertion = "} ";
146   } else {
147     EndLoc = findEndLocation(*S, SM, LangOpts);
148     ClosingInsertion = "\n}";
149   }
150 
151   assert(StartLoc.isValid());
152 
153   // Change only if StartLoc and EndLoc are on the same macro expansion level.
154   // This will also catch invalid EndLoc.
155   // Example: LLVM_DEBUG( for(...) do_something() );
156   // In this case fix-it cannot be provided as the semicolon which is not
157   // visible here is part of the macro. Adding braces here would require adding
158   // another semicolon.
159   if (Lexer::makeFileCharRange(
160           CharSourceRange::getTokenRange(SourceRange(
161               SM.getSpellingLoc(StartLoc), SM.getSpellingLoc(EndLoc))),
162           SM, LangOpts)
163           .isInvalid())
164     return {StartLoc};
165   return {StartLoc, EndLoc, ClosingInsertion};
166 }
167 
168 } // namespace clang::tidy::utils
169