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 "../utils/LexerUtils.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/AST/Decl.h"
13#include "clang/ASTMatchers/ASTMatchFinder.h"
14#include "clang/Basic/SourceLocation.h"
15#include <optional>
16
17namespace clang::tidy::modernize {
18
19static bool locationsInSameFile(const SourceManager &Sources,
20 SourceLocation Loc1, SourceLocation Loc2) {
21 return Loc1.isFileID() && Loc2.isFileID() &&
22 Sources.getFileID(SpellingLoc: Loc1) == Sources.getFileID(SpellingLoc: Loc2);
23}
24
25static StringRef getRawStringRef(const SourceRange &Range,
26 const SourceManager &Sources,
27 const LangOptions &LangOpts) {
28 CharSourceRange TextRange = Lexer::getAsCharRange(Range, SM: Sources, LangOpts);
29 return Lexer::getSourceText(Range: TextRange, SM: Sources, LangOpts);
30}
31
32std::optional<SourceRange>
33NS::getCleanedNamespaceFrontRange(const SourceManager &SM,
34 const LangOptions &LangOpts) const {
35 // Front from namespace tp '{'
36 std::optional<Token> Tok =
37 ::clang::tidy::utils::lexer::findNextTokenSkippingComments(
38 Start: back()->getLocation(), SM, LangOpts);
39 if (!Tok)
40 return std::nullopt;
41 while (Tok->getKind() != tok::TokenKind::l_brace) {
42 Tok = utils::lexer::findNextTokenSkippingComments(Start: Tok->getEndLoc(), SM,
43 LangOpts);
44 if (!Tok)
45 return std::nullopt;
46 }
47 return SourceRange{front()->getBeginLoc(), Tok->getEndLoc()};
48}
49SourceRange NS::getReplacedNamespaceFrontRange() const {
50 return SourceRange{front()->getBeginLoc(), back()->getLocation()};
51}
52
53SourceRange NS::getDefaultNamespaceBackRange() const {
54 return SourceRange{front()->getRBraceLoc(), front()->getRBraceLoc()};
55}
56SourceRange NS::getNamespaceBackRange(const SourceManager &SM,
57 const LangOptions &LangOpts) const {
58 // Back from '}' to conditional '// namespace xxx'
59 SourceLocation Loc = front()->getRBraceLoc();
60 std::optional<Token> Tok =
61 utils::lexer::findNextTokenIncludingComments(Start: Loc, SM, LangOpts);
62 if (!Tok)
63 return getDefaultNamespaceBackRange();
64 if (Tok->getKind() != tok::TokenKind::comment)
65 return getDefaultNamespaceBackRange();
66 SourceRange TokRange = SourceRange{Tok->getLocation(), Tok->getEndLoc()};
67 StringRef TokText = getRawStringRef(Range: TokRange, Sources: SM, LangOpts);
68 NamespaceName CloseComment{"namespace "};
69 appendCloseComment(Str&: CloseComment);
70 // current fix hint in readability/NamespaceCommentCheck.cpp use single line
71 // comment
72 constexpr size_t L = sizeof("//") - 1U;
73 if (TokText.take_front(N: L) == "//" &&
74 TokText.drop_front(N: L).trim() != CloseComment)
75 return getDefaultNamespaceBackRange();
76 return SourceRange{front()->getRBraceLoc(), Tok->getEndLoc()};
77}
78
79void NS::appendName(NamespaceName &Str) const {
80 for (const NamespaceDecl *ND : *this) {
81 if (ND->isInlineNamespace())
82 Str.append(RHS: "inline ");
83 Str.append(ND->getName());
84 if (ND != back())
85 Str.append(RHS: "::");
86 }
87}
88void NS::appendCloseComment(NamespaceName &Str) const {
89 if (size() == 1)
90 Str.append(back()->getName());
91 else
92 appendName(Str);
93}
94
95bool ConcatNestedNamespacesCheck::unsupportedNamespace(const NamespaceDecl &ND,
96 bool IsChild) const {
97 if (ND.isAnonymousNamespace() || !ND.attrs().empty())
98 return true;
99 if (getLangOpts().CPlusPlus20) {
100 // C++20 support inline nested namespace
101 bool IsFirstNS = IsChild || !Namespaces.empty();
102 return ND.isInlineNamespace() && !IsFirstNS;
103 }
104 return ND.isInlineNamespace();
105}
106
107bool ConcatNestedNamespacesCheck::singleNamedNamespaceChild(
108 const NamespaceDecl &ND) const {
109 NamespaceDecl::decl_range Decls = ND.decls();
110 if (std::distance(first: Decls.begin(), last: Decls.end()) != 1)
111 return false;
112
113 const auto *ChildNamespace = dyn_cast<const NamespaceDecl>(Val: *Decls.begin());
114 return ChildNamespace && !unsupportedNamespace(ND: *ChildNamespace, IsChild: true);
115}
116
117void ConcatNestedNamespacesCheck::registerMatchers(
118 ast_matchers::MatchFinder *Finder) {
119 Finder->addMatcher(NodeMatch: ast_matchers::namespaceDecl().bind(ID: "namespace"), Action: this);
120}
121
122void ConcatNestedNamespacesCheck::reportDiagnostic(
123 const SourceManager &SM, const LangOptions &LangOpts) {
124 DiagnosticBuilder DB =
125 diag(Loc: Namespaces.front().front()->getBeginLoc(),
126 Description: "nested namespaces can be concatenated", Level: DiagnosticIDs::Warning);
127
128 SmallVector<SourceRange, 6> Fronts;
129 Fronts.reserve(N: Namespaces.size() - 1U);
130 SmallVector<SourceRange, 6> Backs;
131 Backs.reserve(N: Namespaces.size());
132
133 for (const NS &ND : Namespaces) {
134 std::optional<SourceRange> SR =
135 ND.getCleanedNamespaceFrontRange(SM, LangOpts);
136 if (!SR)
137 return;
138 Fronts.push_back(Elt: SR.value());
139 Backs.push_back(Elt: ND.getNamespaceBackRange(SM, LangOpts));
140 }
141 if (Fronts.empty() || Backs.empty())
142 return;
143
144 // the last one should be handled specially
145 Fronts.pop_back();
146 SourceRange LastRBrace = Backs.pop_back_val();
147
148 NamespaceName ConcatNameSpace{"namespace "};
149 for (const NS &NS : Namespaces) {
150 NS.appendName(Str&: ConcatNameSpace);
151 if (&NS != &Namespaces.back()) // compare address directly
152 ConcatNameSpace.append(RHS: "::");
153 }
154
155 for (SourceRange const &Front : Fronts)
156 DB << FixItHint::CreateRemoval(RemoveRange: Front);
157 DB << FixItHint::CreateReplacement(
158 RemoveRange: Namespaces.back().getReplacedNamespaceFrontRange(), Code: ConcatNameSpace);
159 if (LastRBrace != Namespaces.back().getDefaultNamespaceBackRange())
160 DB << FixItHint::CreateReplacement(RemoveRange: LastRBrace,
161 Code: ("} // " + ConcatNameSpace).str());
162 for (SourceRange const &Back : llvm::reverse(C&: Backs))
163 DB << FixItHint::CreateRemoval(RemoveRange: Back);
164}
165
166void ConcatNestedNamespacesCheck::check(
167 const ast_matchers::MatchFinder::MatchResult &Result) {
168 const NamespaceDecl &ND = *Result.Nodes.getNodeAs<NamespaceDecl>(ID: "namespace");
169 const SourceManager &Sources = *Result.SourceManager;
170
171 if (!locationsInSameFile(Sources, Loc1: ND.getBeginLoc(), Loc2: ND.getRBraceLoc()))
172 return;
173
174 if (unsupportedNamespace(ND, IsChild: false))
175 return;
176
177 if (!ND.isNested())
178 Namespaces.push_back(Elt: NS{});
179 if (!Namespaces.empty())
180 // Otherwise it will crash with invalid input like `inline namespace
181 // a::b::c`.
182 Namespaces.back().push_back(Elt: &ND);
183
184 if (singleNamedNamespaceChild(ND))
185 return;
186
187 if (Namespaces.size() > 1)
188 reportDiagnostic(SM: Sources, LangOpts: getLangOpts());
189
190 Namespaces.clear();
191}
192
193} // namespace clang::tidy::modernize
194

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang-tools-extra/clang-tidy/modernize/ConcatNestedNamespacesCheck.cpp