| //===--- ConcatNestedNamespacesCheck.cpp - clang-tidy----------------------===// | 
 | // | 
 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
 | // See https://llvm.org/LICENSE.txt for license information. | 
 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
 | // | 
 | //===----------------------------------------------------------------------===// | 
 |  | 
 | #include "ConcatNestedNamespacesCheck.h" | 
 | #include "../utils/LexerUtils.h" | 
 | #include "clang/AST/ASTContext.h" | 
 | #include "clang/AST/Decl.h" | 
 | #include "clang/ASTMatchers/ASTMatchFinder.h" | 
 | #include "clang/Basic/SourceLocation.h" | 
 | #include <algorithm> | 
 | #include <optional> | 
 |  | 
 | namespace clang::tidy::modernize { | 
 |  | 
 | static bool locationsInSameFile(const SourceManager &Sources, | 
 |                                 SourceLocation Loc1, SourceLocation Loc2) { | 
 |   return Loc1.isFileID() && Loc2.isFileID() && | 
 |          Sources.getFileID(Loc1) == Sources.getFileID(Loc2); | 
 | } | 
 |  | 
 | static StringRef getRawStringRef(const SourceRange &Range, | 
 |                                  const SourceManager &Sources, | 
 |                                  const LangOptions &LangOpts) { | 
 |   CharSourceRange TextRange = Lexer::getAsCharRange(Range, Sources, LangOpts); | 
 |   return Lexer::getSourceText(TextRange, Sources, LangOpts); | 
 | } | 
 |  | 
 | std::optional<SourceRange> | 
 | NS::getCleanedNamespaceFrontRange(const SourceManager &SM, | 
 |                                   const LangOptions &LangOpts) const { | 
 |   // Front from namespace tp '{' | 
 |   std::optional<Token> Tok = | 
 |       ::clang::tidy::utils::lexer::findNextTokenSkippingComments( | 
 |           back()->getLocation(), SM, LangOpts); | 
 |   if (!Tok) | 
 |     return std::nullopt; | 
 |   while (Tok->getKind() != tok::TokenKind::l_brace) { | 
 |     Tok = utils::lexer::findNextTokenSkippingComments(Tok->getEndLoc(), SM, | 
 |                                                       LangOpts); | 
 |     if (!Tok) | 
 |       return std::nullopt; | 
 |   } | 
 |   return SourceRange{front()->getBeginLoc(), Tok->getEndLoc()}; | 
 | } | 
 | SourceRange NS::getReplacedNamespaceFrontRange() const { | 
 |   return SourceRange{front()->getBeginLoc(), back()->getLocation()}; | 
 | } | 
 |  | 
 | SourceRange NS::getDefaultNamespaceBackRange() const { | 
 |   return SourceRange{front()->getRBraceLoc(), front()->getRBraceLoc()}; | 
 | } | 
 | SourceRange NS::getNamespaceBackRange(const SourceManager &SM, | 
 |                                       const LangOptions &LangOpts) const { | 
 |   // Back from '}' to conditional '// namespace xxx' | 
 |   SourceLocation Loc = front()->getRBraceLoc(); | 
 |   std::optional<Token> Tok = | 
 |       utils::lexer::findNextTokenIncludingComments(Loc, SM, LangOpts); | 
 |   if (!Tok) | 
 |     return getDefaultNamespaceBackRange(); | 
 |   if (Tok->getKind() != tok::TokenKind::comment) | 
 |     return getDefaultNamespaceBackRange(); | 
 |   SourceRange TokRange = SourceRange{Tok->getLocation(), Tok->getEndLoc()}; | 
 |   StringRef TokText = getRawStringRef(TokRange, SM, LangOpts); | 
 |   NamespaceName CloseComment{"namespace "}; | 
 |   appendCloseComment(CloseComment); | 
 |   // current fix hint in readability/NamespaceCommentCheck.cpp use single line | 
 |   // comment | 
 |   constexpr size_t L = sizeof("//") - 1U; | 
 |   if (TokText.take_front(L) == "//" && | 
 |       TokText.drop_front(L).trim() != CloseComment) | 
 |     return getDefaultNamespaceBackRange(); | 
 |   return SourceRange{front()->getRBraceLoc(), Tok->getEndLoc()}; | 
 | } | 
 |  | 
 | void NS::appendName(NamespaceName &Str) const { | 
 |   for (const NamespaceDecl *ND : *this) { | 
 |     if (ND->isInlineNamespace()) | 
 |       Str.append("inline "); | 
 |     Str.append(ND->getName()); | 
 |     if (ND != back()) | 
 |       Str.append("::"); | 
 |   } | 
 | } | 
 | void NS::appendCloseComment(NamespaceName &Str) const { | 
 |   if (size() == 1) | 
 |     Str.append(back()->getName()); | 
 |   else | 
 |     appendName(Str); | 
 | } | 
 |  | 
 | bool ConcatNestedNamespacesCheck::unsupportedNamespace(const NamespaceDecl &ND, | 
 |                                                        bool IsChild) const { | 
 |   if (ND.isAnonymousNamespace() || !ND.attrs().empty()) | 
 |     return true; | 
 |   if (getLangOpts().CPlusPlus20) { | 
 |     // C++20 support inline nested namespace | 
 |     bool IsFirstNS = IsChild || !Namespaces.empty(); | 
 |     return ND.isInlineNamespace() && !IsFirstNS; | 
 |   } | 
 |   return ND.isInlineNamespace(); | 
 | } | 
 |  | 
 | bool ConcatNestedNamespacesCheck::singleNamedNamespaceChild( | 
 |     const NamespaceDecl &ND) const { | 
 |   NamespaceDecl::decl_range Decls = ND.decls(); | 
 |   if (std::distance(Decls.begin(), Decls.end()) != 1) | 
 |     return false; | 
 |  | 
 |   const auto *ChildNamespace = dyn_cast<const NamespaceDecl>(*Decls.begin()); | 
 |   return ChildNamespace && !unsupportedNamespace(*ChildNamespace, true); | 
 | } | 
 |  | 
 | void ConcatNestedNamespacesCheck::registerMatchers( | 
 |     ast_matchers::MatchFinder *Finder) { | 
 |   Finder->addMatcher(ast_matchers::namespaceDecl().bind("namespace"), this); | 
 | } | 
 |  | 
 | void ConcatNestedNamespacesCheck::reportDiagnostic( | 
 |     const SourceManager &SM, const LangOptions &LangOpts) { | 
 |   DiagnosticBuilder DB = | 
 |       diag(Namespaces.front().front()->getBeginLoc(), | 
 |            "nested namespaces can be concatenated", DiagnosticIDs::Warning); | 
 |  | 
 |   SmallVector<SourceRange, 6> Fronts; | 
 |   Fronts.reserve(Namespaces.size() - 1U); | 
 |   SmallVector<SourceRange, 6> Backs; | 
 |   Backs.reserve(Namespaces.size()); | 
 |  | 
 |   for (const NS &ND : Namespaces) { | 
 |     std::optional<SourceRange> SR = | 
 |         ND.getCleanedNamespaceFrontRange(SM, LangOpts); | 
 |     if (!SR) | 
 |       return; | 
 |     Fronts.push_back(SR.value()); | 
 |     Backs.push_back(ND.getNamespaceBackRange(SM, LangOpts)); | 
 |   } | 
 |   if (Fronts.empty() || Backs.empty()) | 
 |     return; | 
 |  | 
 |   // the last one should be handled specially | 
 |   Fronts.pop_back(); | 
 |   SourceRange LastRBrace = Backs.pop_back_val(); | 
 |  | 
 |   NamespaceName ConcatNameSpace{"namespace "}; | 
 |   for (const NS &NS : Namespaces) { | 
 |     NS.appendName(ConcatNameSpace); | 
 |     if (&NS != &Namespaces.back()) // compare address directly | 
 |       ConcatNameSpace.append("::"); | 
 |   } | 
 |  | 
 |   for (SourceRange const &Front : Fronts) | 
 |     DB << FixItHint::CreateRemoval(Front); | 
 |   DB << FixItHint::CreateReplacement( | 
 |       Namespaces.back().getReplacedNamespaceFrontRange(), ConcatNameSpace); | 
 |   if (LastRBrace != Namespaces.back().getDefaultNamespaceBackRange()) | 
 |     DB << FixItHint::CreateReplacement(LastRBrace, | 
 |                                        ("} // " + ConcatNameSpace).str()); | 
 |   for (SourceRange const &Back : llvm::reverse(Backs)) | 
 |     DB << FixItHint::CreateRemoval(Back); | 
 | } | 
 |  | 
 | void ConcatNestedNamespacesCheck::check( | 
 |     const ast_matchers::MatchFinder::MatchResult &Result) { | 
 |   const NamespaceDecl &ND = *Result.Nodes.getNodeAs<NamespaceDecl>("namespace"); | 
 |   const SourceManager &Sources = *Result.SourceManager; | 
 |  | 
 |   if (!locationsInSameFile(Sources, ND.getBeginLoc(), ND.getRBraceLoc())) | 
 |     return; | 
 |  | 
 |   if (unsupportedNamespace(ND, false)) | 
 |     return; | 
 |  | 
 |   if (!ND.isNested()) | 
 |     Namespaces.push_back(NS{}); | 
 |   if (!Namespaces.empty()) | 
 |     // Otherwise it will crash with invalid input like `inline namespace | 
 |     // a::b::c`. | 
 |     Namespaces.back().push_back(&ND); | 
 |  | 
 |   if (singleNamedNamespaceChild(ND)) | 
 |     return; | 
 |  | 
 |   if (Namespaces.size() > 1) | 
 |     reportDiagnostic(Sources, getLangOpts()); | 
 |  | 
 |   Namespaces.clear(); | 
 | } | 
 |  | 
 | } // namespace clang::tidy::modernize |