1 //===--- ConcatNestedNamespacesCheck.cpp - clang-tidy----------------------===// 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 #include "ConcatNestedNamespacesCheck.h" 10 #include "clang/AST/ASTContext.h" 11 #include "clang/ASTMatchers/ASTMatchFinder.h" 12 #include <algorithm> 13 #include <iterator> 14 15 namespace clang { 16 namespace tidy { 17 namespace modernize { 18 19 static 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 25 static bool anonymousOrInlineNamespace(const NamespaceDecl &ND) { 26 return ND.isAnonymousNamespace() || ND.isInlineNamespace(); 27 } 28 29 static bool singleNamedNamespaceChild(const NamespaceDecl &ND) { 30 NamespaceDecl::decl_range Decls = ND.decls(); 31 if (std::distance(Decls.begin(), Decls.end()) != 1) 32 return false; 33 34 const auto *ChildNamespace = dyn_cast<const NamespaceDecl>(*Decls.begin()); 35 return ChildNamespace && !anonymousOrInlineNamespace(*ChildNamespace); 36 } 37 38 static bool alreadyConcatenated(std::size_t NumCandidates, 39 const SourceRange &ReplacementRange, 40 const SourceManager &Sources, 41 const LangOptions &LangOpts) { 42 CharSourceRange TextRange = 43 Lexer::getAsCharRange(ReplacementRange, Sources, LangOpts); 44 StringRef CurrentNamespacesText = 45 Lexer::getSourceText(TextRange, Sources, LangOpts); 46 return CurrentNamespacesText.count(':') == (NumCandidates - 1) * 2; 47 } 48 49 ConcatNestedNamespacesCheck::NamespaceString 50 ConcatNestedNamespacesCheck::concatNamespaces() { 51 NamespaceString Result("namespace "); 52 Result.append(Namespaces.front()->getName()); 53 54 std::for_each(std::next(Namespaces.begin()), Namespaces.end(), 55 [&Result](const NamespaceDecl *ND) { 56 Result.append("::"); 57 Result.append(ND->getName()); 58 }); 59 60 return Result; 61 } 62 63 void ConcatNestedNamespacesCheck::registerMatchers( 64 ast_matchers::MatchFinder *Finder) { 65 if (!getLangOpts().CPlusPlus17) 66 return; 67 68 Finder->addMatcher(ast_matchers::namespaceDecl().bind("namespace"), this); 69 } 70 71 void ConcatNestedNamespacesCheck::reportDiagnostic( 72 const SourceRange &FrontReplacement, const SourceRange &BackReplacement) { 73 diag(Namespaces.front()->getBeginLoc(), 74 "nested namespaces can be concatenated", DiagnosticIDs::Warning) 75 << FixItHint::CreateReplacement(FrontReplacement, concatNamespaces()) 76 << FixItHint::CreateReplacement(BackReplacement, "}"); 77 } 78 79 void ConcatNestedNamespacesCheck::check( 80 const ast_matchers::MatchFinder::MatchResult &Result) { 81 const NamespaceDecl &ND = *Result.Nodes.getNodeAs<NamespaceDecl>("namespace"); 82 const SourceManager &Sources = *Result.SourceManager; 83 84 if (!locationsInSameFile(Sources, ND.getBeginLoc(), ND.getRBraceLoc())) 85 return; 86 87 if (!Sources.isInMainFile(ND.getBeginLoc())) 88 return; 89 90 if (anonymousOrInlineNamespace(ND)) 91 return; 92 93 Namespaces.push_back(&ND); 94 95 if (singleNamedNamespaceChild(ND)) 96 return; 97 98 SourceRange FrontReplacement(Namespaces.front()->getBeginLoc(), 99 Namespaces.back()->getLocation()); 100 SourceRange BackReplacement(Namespaces.back()->getRBraceLoc(), 101 Namespaces.front()->getRBraceLoc()); 102 103 if (!alreadyConcatenated(Namespaces.size(), FrontReplacement, Sources, 104 getLangOpts())) 105 reportDiagnostic(FrontReplacement, BackReplacement); 106 107 Namespaces.clear(); 108 } 109 110 } // namespace modernize 111 } // namespace tidy 112 } // namespace clang 113