clang-tools 22.0.0git
ConcatNestedNamespacesCheck.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
10#include "../utils/LexerUtils.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/AST/Decl.h"
13#include "clang/ASTMatchers/ASTMatchFinder.h"
14#include "clang/Basic/SourceLocation.h"
15#include <optional>
16
17namespace clang::tidy::modernize {
18
19static bool locationsInSameFile(const SourceManager &Sources,
20 SourceLocation Loc1, SourceLocation Loc2) {
21 return Loc1.isFileID() && Loc2.isFileID() &&
22 Sources.getFileID(Loc1) == Sources.getFileID(Loc2);
23}
24
25static StringRef getRawStringRef(const SourceRange &Range,
26 const SourceManager &Sources,
27 const LangOptions &LangOpts) {
28 const CharSourceRange TextRange =
29 Lexer::getAsCharRange(Range, Sources, LangOpts);
30 return Lexer::getSourceText(TextRange, Sources, LangOpts);
31}
32
33std::optional<SourceRange>
34NS::getCleanedNamespaceFrontRange(const SourceManager &SM,
35 const LangOptions &LangOpts) const {
36 // Front from namespace tp '{'
37 std::optional<Token> Tok =
39 back()->getLocation(), SM, LangOpts);
40 if (!Tok)
41 return std::nullopt;
42 while (Tok->getKind() != tok::TokenKind::l_brace) {
43 Tok = utils::lexer::findNextTokenSkippingComments(Tok->getEndLoc(), SM,
44 LangOpts);
45 if (!Tok)
46 return std::nullopt;
47 }
48 return SourceRange{front()->getBeginLoc(), Tok->getEndLoc()};
49}
51 return SourceRange{front()->getBeginLoc(), back()->getLocation()};
52}
53
55 return SourceRange{front()->getRBraceLoc(), front()->getRBraceLoc()};
56}
57SourceRange NS::getNamespaceBackRange(const SourceManager &SM,
58 const LangOptions &LangOpts) const {
59 // Back from '}' to conditional '// namespace xxx'
60 const SourceLocation Loc = front()->getRBraceLoc();
61 std::optional<Token> Tok =
63 if (!Tok)
65 if (Tok->getKind() != tok::TokenKind::comment)
67 const SourceRange TokRange =
68 SourceRange{Tok->getLocation(), Tok->getEndLoc()};
69 const StringRef TokText = getRawStringRef(TokRange, SM, LangOpts);
70 NamespaceName CloseComment{"namespace "};
71 appendCloseComment(CloseComment);
72 // current fix hint in readability/NamespaceCommentCheck.cpp use single line
73 // comment
74 constexpr size_t L = sizeof("//") - 1U;
75 if (TokText.take_front(L) == "//" &&
76 TokText.drop_front(L).trim() != CloseComment)
78 return SourceRange{front()->getRBraceLoc(), Tok->getEndLoc()};
79}
80
81void NS::appendName(NamespaceName &Str) const {
82 for (const NamespaceDecl *ND : *this) {
83 if (ND->isInlineNamespace())
84 Str.append("inline ");
85 Str.append(ND->getName());
86 if (ND != back())
87 Str.append("::");
88 }
89}
91 if (size() == 1)
92 Str.append(back()->getName());
93 else
94 appendName(Str);
95}
96
98 bool IsChild) const {
99 if (ND.isAnonymousNamespace() || !ND.attrs().empty())
100 return true;
101 if (getLangOpts().CPlusPlus20) {
102 // C++20 support inline nested namespace
103 const bool IsFirstNS = IsChild || !Namespaces.empty();
104 return ND.isInlineNamespace() && !IsFirstNS;
105 }
106 return ND.isInlineNamespace();
107}
108
110 const NamespaceDecl &ND) const {
111 const NamespaceDecl::decl_range Decls = ND.decls();
112 if (std::distance(Decls.begin(), Decls.end()) != 1)
113 return false;
114
115 const auto *ChildNamespace = dyn_cast<const NamespaceDecl>(*Decls.begin());
116 return ChildNamespace && !unsupportedNamespace(*ChildNamespace, true);
117}
118
120 ast_matchers::MatchFinder *Finder) {
121 Finder->addMatcher(ast_matchers::namespaceDecl().bind("namespace"), this);
122}
123
124void ConcatNestedNamespacesCheck::reportDiagnostic(
125 const SourceManager &SM, const LangOptions &LangOpts) {
126 const DiagnosticBuilder DB =
127 diag(Namespaces.front().front()->getBeginLoc(),
128 "nested namespaces can be concatenated", DiagnosticIDs::Warning);
129
130 SmallVector<SourceRange, 6> Fronts;
131 Fronts.reserve(Namespaces.size() - 1U);
132 SmallVector<SourceRange, 6> Backs;
133 Backs.reserve(Namespaces.size());
134
135 for (const NS &ND : Namespaces) {
136 std::optional<SourceRange> SR =
137 ND.getCleanedNamespaceFrontRange(SM, LangOpts);
138 if (!SR)
139 return;
140 Fronts.push_back(SR.value());
141 Backs.push_back(ND.getNamespaceBackRange(SM, LangOpts));
142 }
143 if (Fronts.empty() || Backs.empty())
144 return;
145
146 // the last one should be handled specially
147 Fronts.pop_back();
148 const SourceRange LastRBrace = Backs.pop_back_val();
149
150 NamespaceName ConcatNameSpace{"namespace "};
151 for (const NS &NS : Namespaces) {
152 NS.appendName(ConcatNameSpace);
153 if (&NS != &Namespaces.back()) // compare address directly
154 ConcatNameSpace.append("::");
155 }
156
157 for (const SourceRange &Front : Fronts)
158 DB << FixItHint::CreateRemoval(Front);
159 DB << FixItHint::CreateReplacement(
160 Namespaces.back().getReplacedNamespaceFrontRange(), ConcatNameSpace);
161 if (LastRBrace != Namespaces.back().getDefaultNamespaceBackRange())
162 DB << FixItHint::CreateReplacement(LastRBrace,
163 ("} // " + ConcatNameSpace).str());
164 for (const SourceRange &Back : llvm::reverse(Backs))
165 DB << FixItHint::CreateRemoval(Back);
166}
167
169 const ast_matchers::MatchFinder::MatchResult &Result) {
170 const NamespaceDecl &ND = *Result.Nodes.getNodeAs<NamespaceDecl>("namespace");
171 const SourceManager &Sources = *Result.SourceManager;
172
173 if (!locationsInSameFile(Sources, ND.getBeginLoc(), ND.getRBraceLoc()))
174 return;
175
176 if (unsupportedNamespace(ND, false))
177 return;
178
179 if (!ND.isNested())
180 Namespaces.push_back(NS{});
181 if (!Namespaces.empty())
182 // Otherwise it will crash with invalid input like `inline namespace
183 // a::b::c`.
184 Namespaces.back().push_back(&ND);
185
187 return;
188
189 if (Namespaces.size() > 1)
190 reportDiagnostic(Sources, getLangOpts());
191
192 Namespaces.clear();
193}
194
195} // namespace clang::tidy::modernize
bool unsupportedNamespace(const NamespaceDecl &ND, bool IsChild) const
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void appendName(NamespaceName &Str) const
std::optional< SourceRange > getCleanedNamespaceFrontRange(const SourceManager &SM, const LangOptions &LangOpts) const
SourceRange getNamespaceBackRange(const SourceManager &SM, const LangOptions &LangOpts) const
void appendCloseComment(NamespaceName &Str) const
llvm::SmallString< 40 > NamespaceName
static DeclarationName getName(const DependentScopeDeclRefExpr &D)
static StringRef getRawStringRef(const SourceRange &Range, const SourceManager &Sources, const LangOptions &LangOpts)
static bool locationsInSameFile(const SourceManager &Sources, SourceLocation Loc1, SourceLocation Loc2)
std::optional< Token > findNextTokenSkippingComments(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
std::optional< Token > findNextTokenIncludingComments(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
Definition LexerUtils.h:93