xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/ConcatNestedNamespacesCheck.cpp (revision 32aaacc609e7a0523d498b244e081ac6f3df532b)
1d1bd01c3SJonas Toth //===--- ConcatNestedNamespacesCheck.cpp - clang-tidy----------------------===//
2d1bd01c3SJonas Toth //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6d1bd01c3SJonas Toth //
7d1bd01c3SJonas Toth //===----------------------------------------------------------------------===//
8d1bd01c3SJonas Toth 
9d1bd01c3SJonas Toth #include "ConcatNestedNamespacesCheck.h"
1092910a51SCongcong Cai #include "../utils/LexerUtils.h"
11d1bd01c3SJonas Toth #include "clang/AST/ASTContext.h"
1292910a51SCongcong Cai #include "clang/AST/Decl.h"
13d1bd01c3SJonas Toth #include "clang/ASTMatchers/ASTMatchFinder.h"
1492910a51SCongcong Cai #include "clang/Basic/SourceLocation.h"
15d1bd01c3SJonas Toth #include <algorithm>
1692910a51SCongcong Cai #include <optional>
17d1bd01c3SJonas Toth 
187d2ea6c4SCarlos Galvez namespace clang::tidy::modernize {
19d1bd01c3SJonas Toth 
locationsInSameFile(const SourceManager & Sources,SourceLocation Loc1,SourceLocation Loc2)20d1bd01c3SJonas Toth static bool locationsInSameFile(const SourceManager &Sources,
21d1bd01c3SJonas Toth                                 SourceLocation Loc1, SourceLocation Loc2) {
22d1bd01c3SJonas Toth   return Loc1.isFileID() && Loc2.isFileID() &&
23d1bd01c3SJonas Toth          Sources.getFileID(Loc1) == Sources.getFileID(Loc2);
24d1bd01c3SJonas Toth }
25d1bd01c3SJonas Toth 
getRawStringRef(const SourceRange & Range,const SourceManager & Sources,const LangOptions & LangOpts)2692910a51SCongcong Cai static StringRef getRawStringRef(const SourceRange &Range,
2792910a51SCongcong Cai                                  const SourceManager &Sources,
2892910a51SCongcong Cai                                  const LangOptions &LangOpts) {
2992910a51SCongcong Cai   CharSourceRange TextRange = Lexer::getAsCharRange(Range, Sources, LangOpts);
3092910a51SCongcong Cai   return Lexer::getSourceText(TextRange, Sources, LangOpts);
3192910a51SCongcong Cai }
3292910a51SCongcong Cai 
3372777dc0SCongcong Cai std::optional<SourceRange>
getCleanedNamespaceFrontRange(const SourceManager & SM,const LangOptions & LangOpts) const3472777dc0SCongcong Cai NS::getCleanedNamespaceFrontRange(const SourceManager &SM,
3572777dc0SCongcong Cai                                   const LangOptions &LangOpts) const {
3692910a51SCongcong Cai   // Front from namespace tp '{'
3792910a51SCongcong Cai   std::optional<Token> Tok =
3892910a51SCongcong Cai       ::clang::tidy::utils::lexer::findNextTokenSkippingComments(
3972777dc0SCongcong Cai           back()->getLocation(), SM, LangOpts);
4092910a51SCongcong Cai   if (!Tok)
4192910a51SCongcong Cai     return std::nullopt;
4292910a51SCongcong Cai   while (Tok->getKind() != tok::TokenKind::l_brace) {
4392910a51SCongcong Cai     Tok = utils::lexer::findNextTokenSkippingComments(Tok->getEndLoc(), SM,
4492910a51SCongcong Cai                                                       LangOpts);
4592910a51SCongcong Cai     if (!Tok)
4692910a51SCongcong Cai       return std::nullopt;
4792910a51SCongcong Cai   }
4872777dc0SCongcong Cai   return SourceRange{front()->getBeginLoc(), Tok->getEndLoc()};
4972777dc0SCongcong Cai }
getReplacedNamespaceFrontRange() const5072777dc0SCongcong Cai SourceRange NS::getReplacedNamespaceFrontRange() const {
5172777dc0SCongcong Cai   return SourceRange{front()->getBeginLoc(), back()->getLocation()};
5292910a51SCongcong Cai }
5392910a51SCongcong Cai 
getDefaultNamespaceBackRange() const5472777dc0SCongcong Cai SourceRange NS::getDefaultNamespaceBackRange() const {
5572777dc0SCongcong Cai   return SourceRange{front()->getRBraceLoc(), front()->getRBraceLoc()};
5672777dc0SCongcong Cai }
getNamespaceBackRange(const SourceManager & SM,const LangOptions & LangOpts) const5772777dc0SCongcong Cai SourceRange NS::getNamespaceBackRange(const SourceManager &SM,
5872777dc0SCongcong Cai                                       const LangOptions &LangOpts) const {
5992910a51SCongcong Cai   // Back from '}' to conditional '// namespace xxx'
6072777dc0SCongcong Cai   SourceLocation Loc = front()->getRBraceLoc();
6192910a51SCongcong Cai   std::optional<Token> Tok =
6292910a51SCongcong Cai       utils::lexer::findNextTokenIncludingComments(Loc, SM, LangOpts);
6392910a51SCongcong Cai   if (!Tok)
6472777dc0SCongcong Cai     return getDefaultNamespaceBackRange();
6592910a51SCongcong Cai   if (Tok->getKind() != tok::TokenKind::comment)
6672777dc0SCongcong Cai     return getDefaultNamespaceBackRange();
6792910a51SCongcong Cai   SourceRange TokRange = SourceRange{Tok->getLocation(), Tok->getEndLoc()};
6892910a51SCongcong Cai   StringRef TokText = getRawStringRef(TokRange, SM, LangOpts);
69*32aaacc6SCongcong Cai   NamespaceName CloseComment{"namespace "};
70*32aaacc6SCongcong Cai   appendCloseComment(CloseComment);
7192910a51SCongcong Cai   // current fix hint in readability/NamespaceCommentCheck.cpp use single line
7292910a51SCongcong Cai   // comment
73*32aaacc6SCongcong Cai   constexpr size_t L = sizeof("//") - 1U;
74*32aaacc6SCongcong Cai   if (TokText.take_front(L) == "//" &&
75*32aaacc6SCongcong Cai       TokText.drop_front(L).trim() != CloseComment)
7672777dc0SCongcong Cai     return getDefaultNamespaceBackRange();
7772777dc0SCongcong Cai   return SourceRange{front()->getRBraceLoc(), Tok->getEndLoc()};
78d1bd01c3SJonas Toth }
79d1bd01c3SJonas Toth 
appendName(NamespaceName & Str) const80*32aaacc6SCongcong Cai void NS::appendName(NamespaceName &Str) const {
81*32aaacc6SCongcong Cai   for (const NamespaceDecl *ND : *this) {
82*32aaacc6SCongcong Cai     if (ND->isInlineNamespace())
83*32aaacc6SCongcong Cai       Str.append("inline ");
84*32aaacc6SCongcong Cai     Str.append(ND->getName());
85*32aaacc6SCongcong Cai     if (ND != back())
86*32aaacc6SCongcong Cai       Str.append("::");
87*32aaacc6SCongcong Cai   }
88*32aaacc6SCongcong Cai }
appendCloseComment(NamespaceName & Str) const89*32aaacc6SCongcong Cai void NS::appendCloseComment(NamespaceName &Str) const {
90*32aaacc6SCongcong Cai   if (size() == 1)
91*32aaacc6SCongcong Cai     Str.append(back()->getName());
92*32aaacc6SCongcong Cai   else
93*32aaacc6SCongcong Cai     appendName(Str);
94*32aaacc6SCongcong Cai }
95*32aaacc6SCongcong Cai 
unsupportedNamespace(const NamespaceDecl & ND,bool IsChild) const96*32aaacc6SCongcong Cai bool ConcatNestedNamespacesCheck::unsupportedNamespace(const NamespaceDecl &ND,
97*32aaacc6SCongcong Cai                                                        bool IsChild) const {
98*32aaacc6SCongcong Cai   if (ND.isAnonymousNamespace() || !ND.attrs().empty())
99*32aaacc6SCongcong Cai     return true;
100*32aaacc6SCongcong Cai   if (getLangOpts().CPlusPlus20) {
101*32aaacc6SCongcong Cai     // C++20 support inline nested namespace
102*32aaacc6SCongcong Cai     bool IsFirstNS = IsChild || !Namespaces.empty();
103*32aaacc6SCongcong Cai     return ND.isInlineNamespace() && !IsFirstNS;
104*32aaacc6SCongcong Cai   }
105*32aaacc6SCongcong Cai   return ND.isInlineNamespace();
106*32aaacc6SCongcong Cai }
107*32aaacc6SCongcong Cai 
singleNamedNamespaceChild(const NamespaceDecl & ND) const108*32aaacc6SCongcong Cai bool ConcatNestedNamespacesCheck::singleNamedNamespaceChild(
109*32aaacc6SCongcong Cai     const NamespaceDecl &ND) const {
110*32aaacc6SCongcong Cai   NamespaceDecl::decl_range Decls = ND.decls();
111*32aaacc6SCongcong Cai   if (std::distance(Decls.begin(), Decls.end()) != 1)
112*32aaacc6SCongcong Cai     return false;
113*32aaacc6SCongcong Cai 
114*32aaacc6SCongcong Cai   const auto *ChildNamespace = dyn_cast<const NamespaceDecl>(*Decls.begin());
115*32aaacc6SCongcong Cai   return ChildNamespace && !unsupportedNamespace(*ChildNamespace, true);
116d1bd01c3SJonas Toth }
117d1bd01c3SJonas Toth 
registerMatchers(ast_matchers::MatchFinder * Finder)118d1bd01c3SJonas Toth void ConcatNestedNamespacesCheck::registerMatchers(
119d1bd01c3SJonas Toth     ast_matchers::MatchFinder *Finder) {
120d1bd01c3SJonas Toth   Finder->addMatcher(ast_matchers::namespaceDecl().bind("namespace"), this);
121d1bd01c3SJonas Toth }
122d1bd01c3SJonas Toth 
reportDiagnostic(const SourceManager & SM,const LangOptions & LangOpts)123d1bd01c3SJonas Toth void ConcatNestedNamespacesCheck::reportDiagnostic(
12492910a51SCongcong Cai     const SourceManager &SM, const LangOptions &LangOpts) {
12592910a51SCongcong Cai   DiagnosticBuilder DB =
12672777dc0SCongcong Cai       diag(Namespaces.front().front()->getBeginLoc(),
12792910a51SCongcong Cai            "nested namespaces can be concatenated", DiagnosticIDs::Warning);
12892910a51SCongcong Cai 
12992910a51SCongcong Cai   SmallVector<SourceRange, 6> Fronts;
13092910a51SCongcong Cai   Fronts.reserve(Namespaces.size() - 1U);
13192910a51SCongcong Cai   SmallVector<SourceRange, 6> Backs;
13292910a51SCongcong Cai   Backs.reserve(Namespaces.size());
13392910a51SCongcong Cai 
13472777dc0SCongcong Cai   for (const NS &ND : Namespaces) {
13592910a51SCongcong Cai     std::optional<SourceRange> SR =
13672777dc0SCongcong Cai         ND.getCleanedNamespaceFrontRange(SM, LangOpts);
13772777dc0SCongcong Cai     if (!SR)
13892910a51SCongcong Cai       return;
13992910a51SCongcong Cai     Fronts.push_back(SR.value());
14072777dc0SCongcong Cai     Backs.push_back(ND.getNamespaceBackRange(SM, LangOpts));
14192910a51SCongcong Cai   }
14272777dc0SCongcong Cai   if (Fronts.empty() || Backs.empty())
14392910a51SCongcong Cai     return;
14472777dc0SCongcong Cai 
14592910a51SCongcong Cai   // the last one should be handled specially
14692910a51SCongcong Cai   Fronts.pop_back();
14792910a51SCongcong Cai   SourceRange LastRBrace = Backs.pop_back_val();
14872777dc0SCongcong Cai 
14972777dc0SCongcong Cai   NamespaceName ConcatNameSpace{"namespace "};
150*32aaacc6SCongcong Cai   for (const NS &NS : Namespaces) {
151*32aaacc6SCongcong Cai     NS.appendName(ConcatNameSpace);
152*32aaacc6SCongcong Cai     if (&NS != &Namespaces.back()) // compare address directly
153*32aaacc6SCongcong Cai       ConcatNameSpace.append("::");
154*32aaacc6SCongcong Cai   }
15592910a51SCongcong Cai 
15692910a51SCongcong Cai   for (SourceRange const &Front : Fronts)
15792910a51SCongcong Cai     DB << FixItHint::CreateRemoval(Front);
15892910a51SCongcong Cai   DB << FixItHint::CreateReplacement(
15972777dc0SCongcong Cai       Namespaces.back().getReplacedNamespaceFrontRange(), ConcatNameSpace);
16072777dc0SCongcong Cai   if (LastRBrace != Namespaces.back().getDefaultNamespaceBackRange())
16192910a51SCongcong Cai     DB << FixItHint::CreateReplacement(LastRBrace,
16292910a51SCongcong Cai                                        ("} // " + ConcatNameSpace).str());
16392910a51SCongcong Cai   for (SourceRange const &Back : llvm::reverse(Backs))
16492910a51SCongcong Cai     DB << FixItHint::CreateRemoval(Back);
165d1bd01c3SJonas Toth }
166d1bd01c3SJonas Toth 
check(const ast_matchers::MatchFinder::MatchResult & Result)167d1bd01c3SJonas Toth void ConcatNestedNamespacesCheck::check(
168d1bd01c3SJonas Toth     const ast_matchers::MatchFinder::MatchResult &Result) {
169d1bd01c3SJonas Toth   const NamespaceDecl &ND = *Result.Nodes.getNodeAs<NamespaceDecl>("namespace");
170d1bd01c3SJonas Toth   const SourceManager &Sources = *Result.SourceManager;
171d1bd01c3SJonas Toth 
172d1bd01c3SJonas Toth   if (!locationsInSameFile(Sources, ND.getBeginLoc(), ND.getRBraceLoc()))
173d1bd01c3SJonas Toth     return;
174d1bd01c3SJonas Toth 
175*32aaacc6SCongcong Cai   if (unsupportedNamespace(ND, false))
176d1bd01c3SJonas Toth     return;
177d1bd01c3SJonas Toth 
17872777dc0SCongcong Cai   if (!ND.isNested())
17972777dc0SCongcong Cai     Namespaces.push_back(NS{});
180*32aaacc6SCongcong Cai   if (!Namespaces.empty())
181*32aaacc6SCongcong Cai     // Otherwise it will crash with invalid input like `inline namespace
182*32aaacc6SCongcong Cai     // a::b::c`.
18372777dc0SCongcong Cai     Namespaces.back().push_back(&ND);
184d1bd01c3SJonas Toth 
185d1bd01c3SJonas Toth   if (singleNamedNamespaceChild(ND))
186d1bd01c3SJonas Toth     return;
187d1bd01c3SJonas Toth 
18872777dc0SCongcong Cai   if (Namespaces.size() > 1)
18992910a51SCongcong Cai     reportDiagnostic(Sources, getLangOpts());
190d1bd01c3SJonas Toth 
191d1bd01c3SJonas Toth   Namespaces.clear();
192d1bd01c3SJonas Toth }
193d1bd01c3SJonas Toth 
1947d2ea6c4SCarlos Galvez } // namespace clang::tidy::modernize
195