1d1bd01c3SJonas Toth //===--- ConcatNestedNamespacesCheck.cpp - clang-tidy----------------------===//
2d1bd01c3SJonas Toth //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6d1bd01c3SJonas Toth //
7d1bd01c3SJonas Toth //===----------------------------------------------------------------------===//
8d1bd01c3SJonas Toth
9d1bd01c3SJonas Toth #include "ConcatNestedNamespacesCheck.h"
1092910a51SCongcong Cai #include "../utils/LexerUtils.h"
11d1bd01c3SJonas Toth #include "clang/AST/ASTContext.h"
1292910a51SCongcong Cai #include "clang/AST/Decl.h"
13d1bd01c3SJonas Toth #include "clang/ASTMatchers/ASTMatchFinder.h"
1492910a51SCongcong Cai #include "clang/Basic/SourceLocation.h"
15d1bd01c3SJonas Toth #include <algorithm>
1692910a51SCongcong Cai #include <optional>
17d1bd01c3SJonas Toth
187d2ea6c4SCarlos Galvez namespace clang::tidy::modernize {
19d1bd01c3SJonas Toth
locationsInSameFile(const SourceManager & Sources,SourceLocation Loc1,SourceLocation Loc2)20d1bd01c3SJonas Toth static bool locationsInSameFile(const SourceManager &Sources,
21d1bd01c3SJonas Toth SourceLocation Loc1, SourceLocation Loc2) {
22d1bd01c3SJonas Toth return Loc1.isFileID() && Loc2.isFileID() &&
23d1bd01c3SJonas Toth Sources.getFileID(Loc1) == Sources.getFileID(Loc2);
24d1bd01c3SJonas Toth }
25d1bd01c3SJonas Toth
getRawStringRef(const SourceRange & Range,const SourceManager & Sources,const LangOptions & LangOpts)2692910a51SCongcong Cai static StringRef getRawStringRef(const SourceRange &Range,
2792910a51SCongcong Cai const SourceManager &Sources,
2892910a51SCongcong Cai const LangOptions &LangOpts) {
2992910a51SCongcong Cai CharSourceRange TextRange = Lexer::getAsCharRange(Range, Sources, LangOpts);
3092910a51SCongcong Cai return Lexer::getSourceText(TextRange, Sources, LangOpts);
3192910a51SCongcong Cai }
3292910a51SCongcong Cai
3372777dc0SCongcong Cai std::optional<SourceRange>
getCleanedNamespaceFrontRange(const SourceManager & SM,const LangOptions & LangOpts) const3472777dc0SCongcong Cai NS::getCleanedNamespaceFrontRange(const SourceManager &SM,
3572777dc0SCongcong Cai const LangOptions &LangOpts) const {
3692910a51SCongcong Cai // Front from namespace tp '{'
3792910a51SCongcong Cai std::optional<Token> Tok =
3892910a51SCongcong Cai ::clang::tidy::utils::lexer::findNextTokenSkippingComments(
3972777dc0SCongcong Cai back()->getLocation(), SM, LangOpts);
4092910a51SCongcong Cai if (!Tok)
4192910a51SCongcong Cai return std::nullopt;
4292910a51SCongcong Cai while (Tok->getKind() != tok::TokenKind::l_brace) {
4392910a51SCongcong Cai Tok = utils::lexer::findNextTokenSkippingComments(Tok->getEndLoc(), SM,
4492910a51SCongcong Cai LangOpts);
4592910a51SCongcong Cai if (!Tok)
4692910a51SCongcong Cai return std::nullopt;
4792910a51SCongcong Cai }
4872777dc0SCongcong Cai return SourceRange{front()->getBeginLoc(), Tok->getEndLoc()};
4972777dc0SCongcong Cai }
getReplacedNamespaceFrontRange() const5072777dc0SCongcong Cai SourceRange NS::getReplacedNamespaceFrontRange() const {
5172777dc0SCongcong Cai return SourceRange{front()->getBeginLoc(), back()->getLocation()};
5292910a51SCongcong Cai }
5392910a51SCongcong Cai
getDefaultNamespaceBackRange() const5472777dc0SCongcong Cai SourceRange NS::getDefaultNamespaceBackRange() const {
5572777dc0SCongcong Cai return SourceRange{front()->getRBraceLoc(), front()->getRBraceLoc()};
5672777dc0SCongcong Cai }
getNamespaceBackRange(const SourceManager & SM,const LangOptions & LangOpts) const5772777dc0SCongcong Cai SourceRange NS::getNamespaceBackRange(const SourceManager &SM,
5872777dc0SCongcong Cai const LangOptions &LangOpts) const {
5992910a51SCongcong Cai // Back from '}' to conditional '// namespace xxx'
6072777dc0SCongcong Cai SourceLocation Loc = front()->getRBraceLoc();
6192910a51SCongcong Cai std::optional<Token> Tok =
6292910a51SCongcong Cai utils::lexer::findNextTokenIncludingComments(Loc, SM, LangOpts);
6392910a51SCongcong Cai if (!Tok)
6472777dc0SCongcong Cai return getDefaultNamespaceBackRange();
6592910a51SCongcong Cai if (Tok->getKind() != tok::TokenKind::comment)
6672777dc0SCongcong Cai return getDefaultNamespaceBackRange();
6792910a51SCongcong Cai SourceRange TokRange = SourceRange{Tok->getLocation(), Tok->getEndLoc()};
6892910a51SCongcong Cai StringRef TokText = getRawStringRef(TokRange, SM, LangOpts);
69*32aaacc6SCongcong Cai NamespaceName CloseComment{"namespace "};
70*32aaacc6SCongcong Cai appendCloseComment(CloseComment);
7192910a51SCongcong Cai // current fix hint in readability/NamespaceCommentCheck.cpp use single line
7292910a51SCongcong Cai // comment
73*32aaacc6SCongcong Cai constexpr size_t L = sizeof("//") - 1U;
74*32aaacc6SCongcong Cai if (TokText.take_front(L) == "//" &&
75*32aaacc6SCongcong Cai TokText.drop_front(L).trim() != CloseComment)
7672777dc0SCongcong Cai return getDefaultNamespaceBackRange();
7772777dc0SCongcong Cai return SourceRange{front()->getRBraceLoc(), Tok->getEndLoc()};
78d1bd01c3SJonas Toth }
79d1bd01c3SJonas Toth
appendName(NamespaceName & Str) const80*32aaacc6SCongcong Cai void NS::appendName(NamespaceName &Str) const {
81*32aaacc6SCongcong Cai for (const NamespaceDecl *ND : *this) {
82*32aaacc6SCongcong Cai if (ND->isInlineNamespace())
83*32aaacc6SCongcong Cai Str.append("inline ");
84*32aaacc6SCongcong Cai Str.append(ND->getName());
85*32aaacc6SCongcong Cai if (ND != back())
86*32aaacc6SCongcong Cai Str.append("::");
87*32aaacc6SCongcong Cai }
88*32aaacc6SCongcong Cai }
appendCloseComment(NamespaceName & Str) const89*32aaacc6SCongcong Cai void NS::appendCloseComment(NamespaceName &Str) const {
90*32aaacc6SCongcong Cai if (size() == 1)
91*32aaacc6SCongcong Cai Str.append(back()->getName());
92*32aaacc6SCongcong Cai else
93*32aaacc6SCongcong Cai appendName(Str);
94*32aaacc6SCongcong Cai }
95*32aaacc6SCongcong Cai
unsupportedNamespace(const NamespaceDecl & ND,bool IsChild) const96*32aaacc6SCongcong Cai bool ConcatNestedNamespacesCheck::unsupportedNamespace(const NamespaceDecl &ND,
97*32aaacc6SCongcong Cai bool IsChild) const {
98*32aaacc6SCongcong Cai if (ND.isAnonymousNamespace() || !ND.attrs().empty())
99*32aaacc6SCongcong Cai return true;
100*32aaacc6SCongcong Cai if (getLangOpts().CPlusPlus20) {
101*32aaacc6SCongcong Cai // C++20 support inline nested namespace
102*32aaacc6SCongcong Cai bool IsFirstNS = IsChild || !Namespaces.empty();
103*32aaacc6SCongcong Cai return ND.isInlineNamespace() && !IsFirstNS;
104*32aaacc6SCongcong Cai }
105*32aaacc6SCongcong Cai return ND.isInlineNamespace();
106*32aaacc6SCongcong Cai }
107*32aaacc6SCongcong Cai
singleNamedNamespaceChild(const NamespaceDecl & ND) const108*32aaacc6SCongcong Cai bool ConcatNestedNamespacesCheck::singleNamedNamespaceChild(
109*32aaacc6SCongcong Cai const NamespaceDecl &ND) const {
110*32aaacc6SCongcong Cai NamespaceDecl::decl_range Decls = ND.decls();
111*32aaacc6SCongcong Cai if (std::distance(Decls.begin(), Decls.end()) != 1)
112*32aaacc6SCongcong Cai return false;
113*32aaacc6SCongcong Cai
114*32aaacc6SCongcong Cai const auto *ChildNamespace = dyn_cast<const NamespaceDecl>(*Decls.begin());
115*32aaacc6SCongcong Cai return ChildNamespace && !unsupportedNamespace(*ChildNamespace, true);
116d1bd01c3SJonas Toth }
117d1bd01c3SJonas Toth
registerMatchers(ast_matchers::MatchFinder * Finder)118d1bd01c3SJonas Toth void ConcatNestedNamespacesCheck::registerMatchers(
119d1bd01c3SJonas Toth ast_matchers::MatchFinder *Finder) {
120d1bd01c3SJonas Toth Finder->addMatcher(ast_matchers::namespaceDecl().bind("namespace"), this);
121d1bd01c3SJonas Toth }
122d1bd01c3SJonas Toth
reportDiagnostic(const SourceManager & SM,const LangOptions & LangOpts)123d1bd01c3SJonas Toth void ConcatNestedNamespacesCheck::reportDiagnostic(
12492910a51SCongcong Cai const SourceManager &SM, const LangOptions &LangOpts) {
12592910a51SCongcong Cai DiagnosticBuilder DB =
12672777dc0SCongcong Cai diag(Namespaces.front().front()->getBeginLoc(),
12792910a51SCongcong Cai "nested namespaces can be concatenated", DiagnosticIDs::Warning);
12892910a51SCongcong Cai
12992910a51SCongcong Cai SmallVector<SourceRange, 6> Fronts;
13092910a51SCongcong Cai Fronts.reserve(Namespaces.size() - 1U);
13192910a51SCongcong Cai SmallVector<SourceRange, 6> Backs;
13292910a51SCongcong Cai Backs.reserve(Namespaces.size());
13392910a51SCongcong Cai
13472777dc0SCongcong Cai for (const NS &ND : Namespaces) {
13592910a51SCongcong Cai std::optional<SourceRange> SR =
13672777dc0SCongcong Cai ND.getCleanedNamespaceFrontRange(SM, LangOpts);
13772777dc0SCongcong Cai if (!SR)
13892910a51SCongcong Cai return;
13992910a51SCongcong Cai Fronts.push_back(SR.value());
14072777dc0SCongcong Cai Backs.push_back(ND.getNamespaceBackRange(SM, LangOpts));
14192910a51SCongcong Cai }
14272777dc0SCongcong Cai if (Fronts.empty() || Backs.empty())
14392910a51SCongcong Cai return;
14472777dc0SCongcong Cai
14592910a51SCongcong Cai // the last one should be handled specially
14692910a51SCongcong Cai Fronts.pop_back();
14792910a51SCongcong Cai SourceRange LastRBrace = Backs.pop_back_val();
14872777dc0SCongcong Cai
14972777dc0SCongcong Cai NamespaceName ConcatNameSpace{"namespace "};
150*32aaacc6SCongcong Cai for (const NS &NS : Namespaces) {
151*32aaacc6SCongcong Cai NS.appendName(ConcatNameSpace);
152*32aaacc6SCongcong Cai if (&NS != &Namespaces.back()) // compare address directly
153*32aaacc6SCongcong Cai ConcatNameSpace.append("::");
154*32aaacc6SCongcong Cai }
15592910a51SCongcong Cai
15692910a51SCongcong Cai for (SourceRange const &Front : Fronts)
15792910a51SCongcong Cai DB << FixItHint::CreateRemoval(Front);
15892910a51SCongcong Cai DB << FixItHint::CreateReplacement(
15972777dc0SCongcong Cai Namespaces.back().getReplacedNamespaceFrontRange(), ConcatNameSpace);
16072777dc0SCongcong Cai if (LastRBrace != Namespaces.back().getDefaultNamespaceBackRange())
16192910a51SCongcong Cai DB << FixItHint::CreateReplacement(LastRBrace,
16292910a51SCongcong Cai ("} // " + ConcatNameSpace).str());
16392910a51SCongcong Cai for (SourceRange const &Back : llvm::reverse(Backs))
16492910a51SCongcong Cai DB << FixItHint::CreateRemoval(Back);
165d1bd01c3SJonas Toth }
166d1bd01c3SJonas Toth
check(const ast_matchers::MatchFinder::MatchResult & Result)167d1bd01c3SJonas Toth void ConcatNestedNamespacesCheck::check(
168d1bd01c3SJonas Toth const ast_matchers::MatchFinder::MatchResult &Result) {
169d1bd01c3SJonas Toth const NamespaceDecl &ND = *Result.Nodes.getNodeAs<NamespaceDecl>("namespace");
170d1bd01c3SJonas Toth const SourceManager &Sources = *Result.SourceManager;
171d1bd01c3SJonas Toth
172d1bd01c3SJonas Toth if (!locationsInSameFile(Sources, ND.getBeginLoc(), ND.getRBraceLoc()))
173d1bd01c3SJonas Toth return;
174d1bd01c3SJonas Toth
175*32aaacc6SCongcong Cai if (unsupportedNamespace(ND, false))
176d1bd01c3SJonas Toth return;
177d1bd01c3SJonas Toth
17872777dc0SCongcong Cai if (!ND.isNested())
17972777dc0SCongcong Cai Namespaces.push_back(NS{});
180*32aaacc6SCongcong Cai if (!Namespaces.empty())
181*32aaacc6SCongcong Cai // Otherwise it will crash with invalid input like `inline namespace
182*32aaacc6SCongcong Cai // a::b::c`.
18372777dc0SCongcong Cai Namespaces.back().push_back(&ND);
184d1bd01c3SJonas Toth
185d1bd01c3SJonas Toth if (singleNamedNamespaceChild(ND))
186d1bd01c3SJonas Toth return;
187d1bd01c3SJonas Toth
18872777dc0SCongcong Cai if (Namespaces.size() > 1)
18992910a51SCongcong Cai reportDiagnostic(Sources, getLangOpts());
190d1bd01c3SJonas Toth
191d1bd01c3SJonas Toth Namespaces.clear();
192d1bd01c3SJonas Toth }
193d1bd01c3SJonas Toth
1947d2ea6c4SCarlos Galvez } // namespace clang::tidy::modernize
195