brintos

brintos / llvm-project-archived public Read only

0
0
Text · 8.3 KiB · 5baa970 Raw
223 lines · cpp
1//===--- RemoveUsingNamespace.cpp --------------------------------*- C++-*-===//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#include "AST.h"9#include "FindTarget.h"10#include "Selection.h"11#include "refactor/Tweak.h"12#include "support/Logger.h"13#include "clang/AST/Decl.h"14#include "clang/AST/DeclBase.h"15#include "clang/AST/DeclCXX.h"16#include "clang/AST/RecursiveASTVisitor.h"17#include "clang/Basic/SourceLocation.h"18#include "clang/Tooling/Core/Replacement.h"19#include <optional>20 21namespace clang {22namespace clangd {23namespace {24/// Removes the 'using namespace' under the cursor and qualifies all accesses in25/// the current file. E.g.,26///   using namespace std;27///   vector<int> foo(std::map<int, int>);28/// Would become:29///   std::vector<int> foo(std::map<int, int>);30/// Currently limited to using namespace directives inside global namespace to31/// simplify implementation. Also the namespace must not contain using32/// directives.33class RemoveUsingNamespace : public Tweak {34public:35  const char *id() const override;36 37  bool prepare(const Selection &Inputs) override;38  Expected<Effect> apply(const Selection &Inputs) override;39  std::string title() const override {40    return "Remove using namespace, re-qualify names instead";41  }42  llvm::StringLiteral kind() const override {43    return CodeAction::REFACTOR_KIND;44  }45 46private:47  const UsingDirectiveDecl *TargetDirective = nullptr;48};49REGISTER_TWEAK(RemoveUsingNamespace)50 51class FindSameUsings : public RecursiveASTVisitor<FindSameUsings> {52public:53  FindSameUsings(const UsingDirectiveDecl &Target,54                 std::vector<const UsingDirectiveDecl *> &Results)55      : TargetNS(Target.getNominatedNamespace()),56        TargetCtx(Target.getDeclContext()), Results(Results) {}57 58  bool VisitUsingDirectiveDecl(UsingDirectiveDecl *D) {59    if (D->getNominatedNamespace() != TargetNS ||60        D->getDeclContext() != TargetCtx)61      return true;62    Results.push_back(D);63    return true;64  }65 66private:67  const NamespaceDecl *TargetNS;68  const DeclContext *TargetCtx;69  std::vector<const UsingDirectiveDecl *> &Results;70};71 72/// Produce edit removing 'using namespace xxx::yyy' and the trailing semicolon.73llvm::Expected<tooling::Replacement>74removeUsingDirective(ASTContext &Ctx, const UsingDirectiveDecl *D) {75  auto &SM = Ctx.getSourceManager();76  std::optional<Token> NextTok =77      Lexer::findNextToken(D->getEndLoc(), SM, Ctx.getLangOpts());78  if (!NextTok || NextTok->isNot(tok::semi))79    return error("no semicolon after using-directive");80  // FIXME: removing the semicolon may be invalid in some obscure cases, e.g.81  //        if (x) using namespace std; else using namespace bar;82  return tooling::Replacement(83      SM,84      CharSourceRange::getTokenRange(D->getBeginLoc(), NextTok->getLocation()),85      "", Ctx.getLangOpts());86}87 88// Returns true iff the parent of the Node is a TUDecl.89bool isTopLevelDecl(const SelectionTree::Node *Node) {90  return Node->Parent && Node->Parent->ASTNode.get<TranslationUnitDecl>();91}92 93// Return true if `LHS` is declared in `RHS`94bool isDeclaredIn(const NamedDecl *LHS, const DeclContext *RHS) {95  const auto *D = LHS->getDeclContext();96  while (D->isInlineNamespace() || D->isTransparentContext()) {97    if (D->Equals(RHS))98      return true;99    D = D->getParent();100  }101  return D->Equals(RHS);102}103 104bool RemoveUsingNamespace::prepare(const Selection &Inputs) {105  // Find the 'using namespace' directive under the cursor.106  auto *CA = Inputs.ASTSelection.commonAncestor();107  if (!CA)108    return false;109  TargetDirective = CA->ASTNode.get<UsingDirectiveDecl>();110  if (!TargetDirective)111    return false;112  if (!isa<Decl>(TargetDirective->getDeclContext()))113    return false;114  // FIXME: Unavailable for namespaces containing using-namespace decl.115  // It is non-trivial to deal with cases where identifiers come from the inner116  // namespace. For example map has to be changed to aa::map.117  // namespace aa {118  //   namespace bb { struct map {}; }119  //   using namespace bb;120  // }121  // using namespace a^a;122  // int main() { map m; }123  // We need to make this aware of the transitive using-namespace decls.124  if (!TargetDirective->getNominatedNamespace()->using_directives().empty())125    return false;126  return isTopLevelDecl(CA);127}128 129Expected<Tweak::Effect> RemoveUsingNamespace::apply(const Selection &Inputs) {130  auto &Ctx = Inputs.AST->getASTContext();131  auto &SM = Ctx.getSourceManager();132  // First, collect *all* using namespace directives that redeclare the same133  // namespace.134  std::vector<const UsingDirectiveDecl *> AllDirectives;135  FindSameUsings(*TargetDirective, AllDirectives).TraverseAST(Ctx);136 137  SourceLocation FirstUsingDirectiveLoc;138  for (auto *D : AllDirectives) {139    if (FirstUsingDirectiveLoc.isInvalid() ||140        SM.isBeforeInTranslationUnit(D->getBeginLoc(), FirstUsingDirectiveLoc))141      FirstUsingDirectiveLoc = D->getBeginLoc();142  }143 144  // Collect all references to symbols from the namespace for which we're145  // removing the directive.146  std::vector<SourceLocation> IdentsToQualify;147  for (auto &D : Inputs.AST->getLocalTopLevelDecls()) {148    findExplicitReferences(149        D,150        [&](ReferenceLoc Ref) {151          if (Ref.Qualifier)152            return; // This reference is already qualified.153 154          for (auto *T : Ref.Targets) {155            if (!isDeclaredIn(T, TargetDirective->getNominatedNamespace()))156              return;157            auto Kind = T->getDeclName().getNameKind();158            // Avoid adding qualifiers before operators, e.g.159            //   using namespace std;160            //   cout << "foo"; // Must not changed to std::cout std:: << "foo"161            if (Kind == DeclarationName::CXXOperatorName)162              return;163            // Avoid adding qualifiers before user-defined literals, e.g.164            //   using namespace std;165            //   auto s = "foo"s; // Must not changed to auto s = "foo" std::s;166            // FIXME: Add a using-directive for user-defined literals167            // declared in an inline namespace, e.g.168            //   using namespace s^td;169            //   int main() { cout << "foo"s; }170            // change to171            //   using namespace std::literals;172            //   int main() { std::cout << "foo"s; }173            if (Kind == DeclarationName::NameKind::CXXLiteralOperatorName)174              return;175          }176          SourceLocation Loc = Ref.NameLoc;177          if (Loc.isMacroID()) {178            // Avoid adding qualifiers before macro expansions, it's probably179            // incorrect, e.g.180            //   namespace std { int foo(); }181            //   #define FOO 1 + foo()182            //   using namespace foo; // provides matrix183            //   auto x = FOO; // Must not changed to auto x = std::FOO184            if (!SM.isMacroArgExpansion(Loc))185              return; // FIXME: report a warning to the users.186            Loc = SM.getFileLoc(Ref.NameLoc);187          }188          assert(Loc.isFileID());189          if (SM.getFileID(Loc) != SM.getMainFileID())190            return; // FIXME: report these to the user as warnings?191          if (SM.isBeforeInTranslationUnit(Loc, FirstUsingDirectiveLoc))192            return; // Directive was not visible before this point.193          IdentsToQualify.push_back(Loc);194        },195        Inputs.AST->getHeuristicResolver());196  }197  // Remove duplicates.198  llvm::sort(IdentsToQualify);199  IdentsToQualify.erase(llvm::unique(IdentsToQualify), IdentsToQualify.end());200 201  // Produce replacements to remove the using directives.202  tooling::Replacements R;203  for (auto *D : AllDirectives) {204    auto RemoveUsing = removeUsingDirective(Ctx, D);205    if (!RemoveUsing)206      return RemoveUsing.takeError();207    if (auto Err = R.add(*RemoveUsing))208      return std::move(Err);209  }210  // Produce replacements to add the qualifiers.211  std::string Qualifier = printUsingNamespaceName(Ctx, *TargetDirective) + "::";212  for (auto Loc : IdentsToQualify) {213    if (auto Err = R.add(tooling::Replacement(Ctx.getSourceManager(), Loc,214                                              /*Length=*/0, Qualifier)))215      return std::move(Err);216  }217  return Effect::mainFileEdit(SM, std::move(R));218}219 220} // namespace221} // namespace clangd222} // namespace clang223