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 "clang/Lex/Lexer.h" 13 #include <algorithm> 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 Finder->addMatcher(ast_matchers::namespaceDecl().bind("namespace"), this); 67 } 68 69 void ConcatNestedNamespacesCheck::reportDiagnostic( 70 const SourceRange &FrontReplacement, const SourceRange &BackReplacement) { 71 diag(Namespaces.front()->getBeginLoc(), 72 "nested namespaces can be concatenated", DiagnosticIDs::Warning) 73 << FixItHint::CreateReplacement(FrontReplacement, concatNamespaces()) 74 << FixItHint::CreateReplacement(BackReplacement, "}"); 75 } 76 77 void ConcatNestedNamespacesCheck::check( 78 const ast_matchers::MatchFinder::MatchResult &Result) { 79 const NamespaceDecl &ND = *Result.Nodes.getNodeAs<NamespaceDecl>("namespace"); 80 const SourceManager &Sources = *Result.SourceManager; 81 82 if (!locationsInSameFile(Sources, ND.getBeginLoc(), ND.getRBraceLoc())) 83 return; 84 85 if (anonymousOrInlineNamespace(ND)) 86 return; 87 88 Namespaces.push_back(&ND); 89 90 if (singleNamedNamespaceChild(ND)) 91 return; 92 93 SourceRange FrontReplacement(Namespaces.front()->getBeginLoc(), 94 Namespaces.back()->getLocation()); 95 SourceRange BackReplacement(Namespaces.back()->getRBraceLoc(), 96 Namespaces.front()->getRBraceLoc()); 97 98 if (!alreadyConcatenated(Namespaces.size(), FrontReplacement, Sources, 99 getLangOpts())) 100 reportDiagnostic(FrontReplacement, BackReplacement); 101 102 Namespaces.clear(); 103 } 104 105 } // namespace modernize 106 } // namespace tidy 107 } // namespace clang 108