brintos

brintos / llvm-project-archived public Read only

0
0
Text · 6.8 KiB · 7c82e9e Raw
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