xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/ConcatNestedNamespacesCheck.cpp (revision 2946cd701067404b99c39fb29dc9c74bd7193eb3)
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