| //===--- 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 "clang/AST/ASTContext.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include <algorithm> |
| #include <iterator> |
| |
| namespace clang { |
| namespace tidy { |
| namespace modernize { |
| |
| static bool locationsInSameFile(const SourceManager &Sources, |
| SourceLocation Loc1, SourceLocation Loc2) { |
| return Loc1.isFileID() && Loc2.isFileID() && |
| Sources.getFileID(Loc1) == Sources.getFileID(Loc2); |
| } |
| |
| static bool anonymousOrInlineNamespace(const NamespaceDecl &ND) { |
| return ND.isAnonymousNamespace() || ND.isInlineNamespace(); |
| } |
| |
| static bool singleNamedNamespaceChild(const NamespaceDecl &ND) { |
| 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 && !anonymousOrInlineNamespace(*ChildNamespace); |
| } |
| |
| static bool alreadyConcatenated(std::size_t NumCandidates, |
| const SourceRange &ReplacementRange, |
| const SourceManager &Sources, |
| const LangOptions &LangOpts) { |
| // FIXME: This logic breaks when there is a comment with ':'s in the middle. |
| CharSourceRange TextRange = |
| Lexer::getAsCharRange(ReplacementRange, Sources, LangOpts); |
| StringRef CurrentNamespacesText = |
| Lexer::getSourceText(TextRange, Sources, LangOpts); |
| return CurrentNamespacesText.count(':') == (NumCandidates - 1) * 2; |
| } |
| |
| ConcatNestedNamespacesCheck::NamespaceString |
| ConcatNestedNamespacesCheck::concatNamespaces() { |
| NamespaceString Result("namespace "); |
| Result.append(Namespaces.front()->getName()); |
| |
| std::for_each(std::next(Namespaces.begin()), Namespaces.end(), |
| [&Result](const NamespaceDecl *ND) { |
| Result.append("::"); |
| Result.append(ND->getName()); |
| }); |
| |
| return Result; |
| } |
| |
| void ConcatNestedNamespacesCheck::registerMatchers( |
| ast_matchers::MatchFinder *Finder) { |
| if (!getLangOpts().CPlusPlus17) |
| return; |
| |
| Finder->addMatcher(ast_matchers::namespaceDecl().bind("namespace"), this); |
| } |
| |
| void ConcatNestedNamespacesCheck::reportDiagnostic( |
| const SourceRange &FrontReplacement, const SourceRange &BackReplacement) { |
| diag(Namespaces.front()->getBeginLoc(), |
| "nested namespaces can be concatenated", DiagnosticIDs::Warning) |
| << FixItHint::CreateReplacement(FrontReplacement, concatNamespaces()) |
| << FixItHint::CreateReplacement(BackReplacement, "}"); |
| } |
| |
| 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 (!Sources.isInMainFile(ND.getBeginLoc())) |
| return; |
| |
| if (anonymousOrInlineNamespace(ND)) |
| return; |
| |
| Namespaces.push_back(&ND); |
| |
| if (singleNamedNamespaceChild(ND)) |
| return; |
| |
| SourceRange FrontReplacement(Namespaces.front()->getBeginLoc(), |
| Namespaces.back()->getLocation()); |
| SourceRange BackReplacement(Namespaces.back()->getRBraceLoc(), |
| Namespaces.front()->getRBraceLoc()); |
| |
| if (!alreadyConcatenated(Namespaces.size(), FrontReplacement, Sources, |
| getLangOpts())) |
| reportDiagnostic(FrontReplacement, BackReplacement); |
| |
| Namespaces.clear(); |
| } |
| |
| } // namespace modernize |
| } // namespace tidy |
| } // namespace clang |