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
locationsInSameFile(const SourceManager & Sources,SourceLocation Loc1,SourceLocation Loc2)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
anonymousOrInlineNamespace(const NamespaceDecl & ND)25 static bool anonymousOrInlineNamespace(const NamespaceDecl &ND) {
26 return ND.isAnonymousNamespace() || ND.isInlineNamespace();
27 }
28
singleNamedNamespaceChild(const NamespaceDecl & ND)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
alreadyConcatenated(std::size_t NumCandidates,const SourceRange & ReplacementRange,const SourceManager & Sources,const LangOptions & LangOpts)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
concatNamespaces()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
registerMatchers(ast_matchers::MatchFinder * Finder)64 void ConcatNestedNamespacesCheck::registerMatchers(
65 ast_matchers::MatchFinder *Finder) {
66 Finder->addMatcher(ast_matchers::namespaceDecl().bind("namespace"), this);
67 }
68
reportDiagnostic(const SourceRange & FrontReplacement,const SourceRange & BackReplacement)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
check(const ast_matchers::MatchFinder::MatchResult & Result)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 (!Sources.isInMainFile(ND.getBeginLoc()))
86 return;
87
88 if (anonymousOrInlineNamespace(ND))
89 return;
90
91 Namespaces.push_back(&ND);
92
93 if (singleNamedNamespaceChild(ND))
94 return;
95
96 SourceRange FrontReplacement(Namespaces.front()->getBeginLoc(),
97 Namespaces.back()->getLocation());
98 SourceRange BackReplacement(Namespaces.back()->getRBraceLoc(),
99 Namespaces.front()->getRBraceLoc());
100
101 if (!alreadyConcatenated(Namespaces.size(), FrontReplacement, Sources,
102 getLangOpts()))
103 reportDiagnostic(FrontReplacement, BackReplacement);
104
105 Namespaces.clear();
106 }
107
108 } // namespace modernize
109 } // namespace tidy
110 } // namespace clang
111