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