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 // FIXME: This logic breaks when there is a comment with ':'s in the middle. 43 CharSourceRange TextRange = 44 Lexer::getAsCharRange(ReplacementRange, Sources, LangOpts); 45 StringRef CurrentNamespacesText = 46 Lexer::getSourceText(TextRange, Sources, LangOpts); 47 return CurrentNamespacesText.count(':') == (NumCandidates - 1) * 2; 48 } 49 50 ConcatNestedNamespacesCheck::NamespaceString 51 ConcatNestedNamespacesCheck::concatNamespaces() { 52 NamespaceString Result("namespace "); 53 Result.append(Namespaces.front()->getName()); 54 55 std::for_each(std::next(Namespaces.begin()), Namespaces.end(), 56 [&Result](const NamespaceDecl *ND) { 57 Result.append("::"); 58 Result.append(ND->getName()); 59 }); 60 61 return Result; 62 } 63 64 void ConcatNestedNamespacesCheck::registerMatchers( 65 ast_matchers::MatchFinder *Finder) { 66 if (!getLangOpts().CPlusPlus17) 67 return; 68 69 Finder->addMatcher(ast_matchers::namespaceDecl().bind("namespace"), this); 70 } 71 72 void ConcatNestedNamespacesCheck::reportDiagnostic( 73 const SourceRange &FrontReplacement, const SourceRange &BackReplacement) { 74 diag(Namespaces.front()->getBeginLoc(), 75 "nested namespaces can be concatenated", DiagnosticIDs::Warning) 76 << FixItHint::CreateReplacement(FrontReplacement, concatNamespaces()) 77 << FixItHint::CreateReplacement(BackReplacement, "}"); 78 } 79 80 void ConcatNestedNamespacesCheck::check( 81 const ast_matchers::MatchFinder::MatchResult &Result) { 82 const NamespaceDecl &ND = *Result.Nodes.getNodeAs<NamespaceDecl>("namespace"); 83 const SourceManager &Sources = *Result.SourceManager; 84 85 if (!locationsInSameFile(Sources, ND.getBeginLoc(), ND.getRBraceLoc())) 86 return; 87 88 if (!Sources.isInMainFile(ND.getBeginLoc())) 89 return; 90 91 if (anonymousOrInlineNamespace(ND)) 92 return; 93 94 Namespaces.push_back(&ND); 95 96 if (singleNamedNamespaceChild(ND)) 97 return; 98 99 SourceRange FrontReplacement(Namespaces.front()->getBeginLoc(), 100 Namespaces.back()->getLocation()); 101 SourceRange BackReplacement(Namespaces.back()->getRBraceLoc(), 102 Namespaces.front()->getRBraceLoc()); 103 104 if (!alreadyConcatenated(Namespaces.size(), FrontReplacement, Sources, 105 getLangOpts())) 106 reportDiagnostic(FrontReplacement, BackReplacement); 107 108 Namespaces.clear(); 109 } 110 111 } // namespace modernize 112 } // namespace tidy 113 } // namespace clang 114