clang-tools 23.0.0git
BracesAroundStatement.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
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
22namespace clang::tidy::utils {
23
24BraceInsertionHints::operator bool() const { return DiagnosticPos.isValid(); }
25
27 return OpeningBracePos.isValid() && ClosingBracePos.isValid();
28}
29
31 const SourceManager &SourceMgr) const {
32 return SourceMgr.getSpellingLineNumber(ClosingBracePos) -
33 SourceMgr.getSpellingLineNumber(OpeningBracePos);
34}
35
37 return OpeningBracePos.isValid()
38 ? FixItHint::CreateInsertion(OpeningBracePos, " {")
39 : FixItHint();
40}
41
43 return ClosingBracePos.isValid()
44 ? FixItHint::CreateInsertion(ClosingBracePos, ClosingBrace)
45 : FixItHint();
46}
47
48static tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM,
49 const LangOptions &LangOpts) {
50 Token Tok;
51 const SourceLocation Beginning =
52 Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
53 const bool Invalid = Lexer::getRawToken(Beginning, Tok, SM, LangOpts);
54 assert(!Invalid && "Expected a valid token.");
55
56 if (Invalid)
57 return tok::NUM_TOKENS;
58
59 return Tok.getKind();
60}
61
62static SourceLocation findEndLocation(const Stmt &S, const SourceManager &SM,
63 const LangOptions &LangOpts) {
64 SourceLocation Loc = lexer::getUnifiedEndLoc(S, SM, LangOpts);
65 if (!Loc.isValid())
66 return Loc;
67
68 // Start searching right after S.
69 Loc = Loc.getLocWithOffset(1);
70
71 for (;;) {
72 assert(Loc.isValid());
73 while (isHorizontalWhitespace(*SM.getCharacterData(Loc)))
74 Loc = Loc.getLocWithOffset(1);
75
76 if (isVerticalWhitespace(*SM.getCharacterData(Loc))) {
77 // EOL, insert brace before.
78 break;
79 }
80 const tok::TokenKind TokKind = getTokenKind(Loc, SM, LangOpts);
81 if (TokKind != tok::comment) {
82 // Non-comment token, insert brace before.
83 break;
84 }
85
86 const SourceLocation TokEndLoc =
87 Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts);
88 const SourceRange TokRange(Loc, TokEndLoc);
89 const StringRef Comment = Lexer::getSourceText(
90 CharSourceRange::getTokenRange(TokRange), SM, LangOpts);
91 if (Comment.starts_with("/*") && Comment.contains('\n')) {
92 // Multi-line block comment, insert brace before.
93 break;
94 }
95 // else: Trailing comment, insert brace after the newline.
96
97 // Fast-forward current token.
98 Loc = TokEndLoc;
99 }
100 return Loc;
101}
102
104 const LangOptions &LangOpts,
105 const SourceManager &SM,
106 SourceLocation StartLoc,
107 SourceLocation EndLocHint) {
108 // 1) If there's a corresponding "else" or "while", the check inserts "} "
109 // right before that token.
110 // 2) If there's a multi-line block comment starting on the same line after
111 // the location we're inserting the closing brace at, or there's a non-comment
112 // token, the check inserts "\n}" right before that token.
113 // 3) If the statement ends in the middle of a macro body expansion, the check
114 // emits a diagnostic without a fix-it.
115 // 4) Otherwise the check finds the end of line (possibly after some block or
116 // line comments) and inserts "\n}" right before that EOL.
117 if (!S || isa<CompoundStmt>(S)) {
118 // Already inside braces.
119 return {};
120 }
121
122 // When TreeTransform, Stmt in constexpr IfStmt will be transform to NullStmt.
123 // This NullStmt can be detected according to beginning token.
124 const SourceLocation StmtBeginLoc = S->getBeginLoc();
125 if (isa<NullStmt>(S) && StmtBeginLoc.isValid() &&
126 getTokenKind(StmtBeginLoc, SM, LangOpts) == tok::l_brace)
127 return {};
128
129 if (StartLoc.isInvalid())
130 return {};
131
132 const Stmt *InnerS = S;
133 while (const auto *AS = dyn_cast<AttributedStmt>(InnerS))
134 InnerS = AS->getSubStmt();
135
136 SourceLocation InsertLoc = StartLoc;
137 if (S != InnerS) {
138 if (std::optional<Token> Tok = utils::lexer::getPreviousToken(
139 InnerS->getBeginLoc(), SM, LangOpts, /*SkipComments=*/true)) {
140 InsertLoc = Tok->getLocation();
141 }
142 }
143
144 // Convert StartLoc to file location, if it's on the same macro expansion
145 // level as the start of the statement. We also need file locations for
146 // Lexer::getLocForEndOfToken working properly.
147 StartLoc = Lexer::makeFileCharRange(CharSourceRange::getCharRange(
148 InsertLoc, InnerS->getBeginLoc()),
149 SM, LangOpts)
150 .getBegin();
151 if (StartLoc.isInvalid())
152 return {};
153 StartLoc = Lexer::getLocForEndOfToken(StartLoc, 0, SM, LangOpts);
154
155 // StartLoc points at the location of the opening brace to be inserted.
156 SourceLocation EndLoc;
157 StringRef ClosingInsertion;
158 if (EndLocHint.isValid()) {
159 EndLoc = EndLocHint;
160 ClosingInsertion = "} ";
161 } else {
162 const SourceLocation StmtEndLoc = S->getEndLoc();
163 if (StmtEndLoc.isMacroID() && SM.isMacroBodyExpansion(StmtEndLoc) &&
164 !Lexer::isAtEndOfMacroExpansion(StmtEndLoc, SM, LangOpts)) {
165 // No safe fix-it for a statement ending in the middle of a macro body.
166 return {StartLoc};
167 }
168 EndLoc = findEndLocation(*S, SM, LangOpts);
169 ClosingInsertion = "\n}";
170 }
171
172 assert(StartLoc.isValid());
173
174 // Change only if StartLoc and EndLoc are on the same macro expansion level.
175 // This will also catch invalid EndLoc.
176 // Example: LLVM_DEBUG( for(...) do_something() );
177 // In this case fix-it cannot be provided as the semicolon which is not
178 // visible here is part of the macro. Adding braces here would require adding
179 // another semicolon.
180 if (Lexer::makeFileCharRange(
181 CharSourceRange::getTokenRange(SourceRange(
182 SM.getSpellingLoc(StartLoc), SM.getSpellingLoc(EndLoc))),
183 SM, LangOpts)
184 .isInvalid())
185 return {StartLoc};
186 return {StartLoc, EndLoc, ClosingInsertion};
187}
188
189} // namespace clang::tidy::utils
This file provides utilities to put braces around a statement.
SourceLocation getUnifiedEndLoc(const Stmt &S, const SourceManager &SM, const LangOptions &LangOpts)
Stmt->getEndLoc does not always behave the same way depending on Token type.
std::optional< Token > getPreviousToken(SourceLocation Location, const SourceManager &SM, const LangOptions &LangOpts, bool SkipComments)
Returns previous token or std::nullopt if not found.
static SourceLocation findEndLocation(const Stmt &S, const SourceManager &SM, const LangOptions &LangOpts)
BraceInsertionHints getBraceInsertionsHints(const Stmt *const S, const LangOptions &LangOpts, const SourceManager &SM, SourceLocation StartLoc, SourceLocation EndLocHint)
Create fix-it hints for braces that wrap the given statement when applied.
static tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts)
A provider of fix-it hints to insert opening and closing braces.
SourceLocation DiagnosticPos
The position of a potential diagnostic.
FixItHint closingBraceFixIt() const
Fix-it to insert a closing brace.
bool offersFixIts() const
Indicates whether the hint provides fix-its to insert braces.
unsigned resultingCompoundLineExtent(const SourceManager &SourceMgr) const
The number of lines between the inserted opening brace and its closing counterpart.
FixItHint openingBraceFixIt() const
Fix-it to insert an opening brace.