1 //===--- ConcatNestedNamespacesCheck.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 "ConcatNestedNamespacesCheck.h"
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"
18 namespace clang::tidy::modernize
{
20 static bool locationsInSameFile(const SourceManager
&Sources
,
21 SourceLocation Loc1
, SourceLocation Loc2
) {
22 return Loc1
.isFileID() && Loc2
.isFileID() &&
23 Sources
.getFileID(Loc1
) == Sources
.getFileID(Loc2
);
26 static StringRef
getRawStringRef(const SourceRange
&Range
,
27 const SourceManager
&Sources
,
28 const LangOptions
&LangOpts
) {
29 CharSourceRange TextRange
= Lexer::getAsCharRange(Range
, Sources
, LangOpts
);
30 return Lexer::getSourceText(TextRange
, Sources
, LangOpts
);
33 std::optional
<SourceRange
>
34 NS::getCleanedNamespaceFrontRange(const SourceManager
&SM
,
35 const LangOptions
&LangOpts
) const {
36 // Front from namespace tp '{'
37 std::optional
<Token
> Tok
=
38 ::clang::tidy::utils::lexer::findNextTokenSkippingComments(
39 back()->getLocation(), SM
, LangOpts
);
42 while (Tok
->getKind() != tok::TokenKind::l_brace
) {
43 Tok
= utils::lexer::findNextTokenSkippingComments(Tok
->getEndLoc(), SM
,
48 return SourceRange
{front()->getBeginLoc(), Tok
->getEndLoc()};
50 SourceRange
NS::getReplacedNamespaceFrontRange() const {
51 return SourceRange
{front()->getBeginLoc(), back()->getLocation()};
54 SourceRange
NS::getDefaultNamespaceBackRange() const {
55 return SourceRange
{front()->getRBraceLoc(), front()->getRBraceLoc()};
57 SourceRange
NS::getNamespaceBackRange(const SourceManager
&SM
,
58 const LangOptions
&LangOpts
) const {
59 // Back from '}' to conditional '// namespace xxx'
60 SourceLocation Loc
= front()->getRBraceLoc();
61 std::optional
<Token
> Tok
=
62 utils::lexer::findNextTokenIncludingComments(Loc
, SM
, LangOpts
);
64 return getDefaultNamespaceBackRange();
65 if (Tok
->getKind() != tok::TokenKind::comment
)
66 return getDefaultNamespaceBackRange();
67 SourceRange TokRange
= SourceRange
{Tok
->getLocation(), Tok
->getEndLoc()};
68 StringRef TokText
= getRawStringRef(TokRange
, SM
, LangOpts
);
69 NamespaceName CloseComment
{"namespace "};
70 appendCloseComment(CloseComment
);
71 // current fix hint in readability/NamespaceCommentCheck.cpp use single line
73 constexpr size_t L
= sizeof("//") - 1U;
74 if (TokText
.take_front(L
) == "//" &&
75 TokText
.drop_front(L
).trim() != CloseComment
)
76 return getDefaultNamespaceBackRange();
77 return SourceRange
{front()->getRBraceLoc(), Tok
->getEndLoc()};
80 void NS::appendName(NamespaceName
&Str
) const {
81 for (const NamespaceDecl
*ND
: *this) {
82 if (ND
->isInlineNamespace())
83 Str
.append("inline ");
84 Str
.append(ND
->getName());
89 void NS::appendCloseComment(NamespaceName
&Str
) const {
91 Str
.append(back()->getName());
96 bool ConcatNestedNamespacesCheck::unsupportedNamespace(const NamespaceDecl
&ND
,
98 if (ND
.isAnonymousNamespace() || !ND
.attrs().empty())
100 if (getLangOpts().CPlusPlus20
) {
101 // C++20 support inline nested namespace
102 bool IsFirstNS
= IsChild
|| !Namespaces
.empty();
103 return ND
.isInlineNamespace() && !IsFirstNS
;
105 return ND
.isInlineNamespace();
108 bool ConcatNestedNamespacesCheck::singleNamedNamespaceChild(
109 const NamespaceDecl
&ND
) const {
110 NamespaceDecl::decl_range Decls
= ND
.decls();
111 if (std::distance(Decls
.begin(), Decls
.end()) != 1)
114 const auto *ChildNamespace
= dyn_cast
<const NamespaceDecl
>(*Decls
.begin());
115 return ChildNamespace
&& !unsupportedNamespace(*ChildNamespace
, true);
118 void ConcatNestedNamespacesCheck::registerMatchers(
119 ast_matchers::MatchFinder
*Finder
) {
120 Finder
->addMatcher(ast_matchers::namespaceDecl().bind("namespace"), this);
123 void ConcatNestedNamespacesCheck::reportDiagnostic(
124 const SourceManager
&SM
, const LangOptions
&LangOpts
) {
125 DiagnosticBuilder DB
=
126 diag(Namespaces
.front().front()->getBeginLoc(),
127 "nested namespaces can be concatenated", DiagnosticIDs::Warning
);
129 SmallVector
<SourceRange
, 6> Fronts
;
130 Fronts
.reserve(Namespaces
.size() - 1U);
131 SmallVector
<SourceRange
, 6> Backs
;
132 Backs
.reserve(Namespaces
.size());
134 for (const NS
&ND
: Namespaces
) {
135 std::optional
<SourceRange
> SR
=
136 ND
.getCleanedNamespaceFrontRange(SM
, LangOpts
);
139 Fronts
.push_back(SR
.value());
140 Backs
.push_back(ND
.getNamespaceBackRange(SM
, LangOpts
));
142 if (Fronts
.empty() || Backs
.empty())
145 // the last one should be handled specially
147 SourceRange LastRBrace
= Backs
.pop_back_val();
149 NamespaceName ConcatNameSpace
{"namespace "};
150 for (const NS
&NS
: Namespaces
) {
151 NS
.appendName(ConcatNameSpace
);
152 if (&NS
!= &Namespaces
.back()) // compare address directly
153 ConcatNameSpace
.append("::");
156 for (SourceRange
const &Front
: Fronts
)
157 DB
<< FixItHint::CreateRemoval(Front
);
158 DB
<< FixItHint::CreateReplacement(
159 Namespaces
.back().getReplacedNamespaceFrontRange(), ConcatNameSpace
);
160 if (LastRBrace
!= Namespaces
.back().getDefaultNamespaceBackRange())
161 DB
<< FixItHint::CreateReplacement(LastRBrace
,
162 ("} // " + ConcatNameSpace
).str());
163 for (SourceRange
const &Back
: llvm::reverse(Backs
))
164 DB
<< FixItHint::CreateRemoval(Back
);
167 void ConcatNestedNamespacesCheck::check(
168 const ast_matchers::MatchFinder::MatchResult
&Result
) {
169 const NamespaceDecl
&ND
= *Result
.Nodes
.getNodeAs
<NamespaceDecl
>("namespace");
170 const SourceManager
&Sources
= *Result
.SourceManager
;
172 if (!locationsInSameFile(Sources
, ND
.getBeginLoc(), ND
.getRBraceLoc()))
175 if (unsupportedNamespace(ND
, false))
179 Namespaces
.push_back(NS
{});
180 if (!Namespaces
.empty())
181 // Otherwise it will crash with invalid input like `inline namespace
183 Namespaces
.back().push_back(&ND
);
185 if (singleNamedNamespaceChild(ND
))
188 if (Namespaces
.size() > 1)
189 reportDiagnostic(Sources
, getLangOpts());
194 } // namespace clang::tidy::modernize