ilya-biryukov created this revision.
ilya-biryukov added a reviewer: sammccall.
Herald added subscribers: kadircet, jfb, arphaman, mgrang, jkorous, MaskRay, 
ioeric, mgorny.

Only available in the source files to fit into the model of single-file
actions. Doing the same in headers would require more complicated
machinery, which is out of scope of code actions.


Repository:
  rCTE Clang Tools Extra

https://reviews.llvm.org/D56612

Files:
  clangd/AST.cpp
  clangd/AST.h
  clangd/CMakeLists.txt
  clangd/CodeActions.cpp
  clangd/refactor/actions/RemoveUsingNamespace.cpp
  clangd/refactor/actions/RemoveUsingNamespace.h

Index: clangd/refactor/actions/RemoveUsingNamespace.h
===================================================================
--- /dev/null
+++ clangd/refactor/actions/RemoveUsingNamespace.h
@@ -0,0 +1,32 @@
+//===--- RemoveUsingNamespace.h ----------------------------------*- C++-*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+// A code action that removes the 'using namespace' under the cursor and
+// qualifies all accesses in the current file. E.g.,
+//   using namespace std;
+//   vector<int> foo(std::map<int, int>);
+// Would become:
+//   std::vector<int> foo(std::map<int, int>);
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_REFACTOR_ACTIONS_REMOVEUSINGNAMESPACE_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_REFACTOR_ACTIONS_REMOVEUSINGNAMESPACE_H
+
+#include "refactor/ActionProvider.h"
+
+namespace clang {
+namespace clangd {
+
+class RemoveUsingNamespace : public ActionProvider {
+  llvm::Optional<PreparedAction> prepare(const ActionInputs &Inputs) override;
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif
Index: clangd/refactor/actions/RemoveUsingNamespace.cpp
===================================================================
--- /dev/null
+++ clangd/refactor/actions/RemoveUsingNamespace.cpp
@@ -0,0 +1,189 @@
+//===--- RemoveUsingNamespace.cpp --------------------------------*- C++-*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#include "RemoveUsingNamespace.h"
+#include "AST.h"
+#include "ClangdUnit.h"
+#include "SourceCode.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/Tooling/Core/Replacement.h"
+#include "clang/Tooling/Refactoring/RecursiveSymbolVisitor.h"
+#include "llvm/ADT/ScopeExit.h"
+
+namespace clang {
+namespace clangd {
+
+namespace {
+class FindNodeUnderCursor : public RecursiveASTVisitor<FindNodeUnderCursor> {
+public:
+  FindNodeUnderCursor(SourceLocation SearchedLoc, UsingDirectiveDecl *&Result)
+      : SearchedLoc(SearchedLoc), Result(Result) {}
+
+  bool VisitUsingDirectiveDecl(UsingDirectiveDecl *D) {
+    if (D->getUsingLoc() != SearchedLoc)
+      return true;
+
+    Result = D;
+    return false;
+  }
+
+private:
+  SourceLocation SearchedLoc;
+  UsingDirectiveDecl *&Result;
+};
+
+class FindSameUsings : public RecursiveASTVisitor<FindSameUsings> {
+public:
+  FindSameUsings(UsingDirectiveDecl &Target,
+                 std::vector<UsingDirectiveDecl *> &Results)
+      : TargetNS(Target.getNominatedNamespace()),
+        TargetCtx(Target.getDeclContext()), Results(Results) {}
+
+  bool VisitUsingDirectiveDecl(UsingDirectiveDecl *D) {
+    if (D->getNominatedNamespace() != TargetNS ||
+        D->getDeclContext() != TargetCtx)
+      return true;
+
+    Results.push_back(D);
+    return true;
+  }
+
+private:
+  NamespaceDecl *TargetNS;
+  DeclContext *TargetCtx;
+  std::vector<UsingDirectiveDecl *> &Results;
+};
+
+class FindIdentsToQualify
+    : public tooling::RecursiveSymbolVisitor<FindIdentsToQualify> {
+public:
+  FindIdentsToQualify(ASTContext &Ctx, NamespaceDecl &TargetNS,
+                      std::vector<SourceLocation> &ResultIdents)
+      : RecursiveSymbolVisitor(Ctx.getSourceManager(), Ctx.getLangOpts()),
+        Ctx(Ctx), TargetNS(TargetNS), ResultIdents(ResultIdents) {}
+
+  bool visitSymbolOccurrence(const NamedDecl *D,
+                             llvm::ArrayRef<SourceRange> NameRanges) {
+    if (!D || D->getCanonicalDecl() == TargetNS.getCanonicalDecl())
+      return true;
+    if (!D->getDeclName().isIdentifier() ||
+        D->getDeclName().getNameKind() == DeclarationName::CXXOperatorName)
+      return true; // do not add qualifiers for non-idents, e.g. 'operator+'.
+    // Check the symbol is unqualified and references something inside our
+    // namespace.
+    // FIXME: add a check it's unqualified.
+    if (!TargetNS.InEnclosingNamespaceSetOf(D->getDeclContext()))
+      return true;
+    // FIXME: handle more tricky cases, e.g. we don't need the qualifier if we
+    //        have the using decls for some entities, we might have qualified
+    //        references that need updating too.
+    for (auto R : NameRanges) {
+      if (!Ctx.getSourceManager().isWrittenInMainFile(R.getBegin()))
+        continue; // we can't fix refences outside our file.
+                  // FIXME: this might be a conflict that we need to report.
+      ResultIdents.push_back(R.getBegin());
+    }
+    return true;
+  }
+
+  bool TraverseDecl(Decl *D) {
+    if (!Ctx.getSourceManager().isWrittenInMainFile(D->getLocation()) &&
+        !isa<TranslationUnitDecl>(D))
+      return true; // skip decls outside main file.
+    return RecursiveSymbolVisitor::TraverseDecl(D);
+  }
+
+private:
+  ASTContext &Ctx;
+  NamespaceDecl &TargetNS;
+  std::vector<SourceLocation> &ResultIdents;
+};
+
+// Produces an edit to remove 'using namespace xxx::yyy' and the trailing
+// semicolon.
+llvm::Expected<tooling::Replacement>
+removeUsingDirective(ASTContext &Ctx, UsingDirectiveDecl *D) {
+  auto &SrcMgr = Ctx.getSourceManager();
+  auto R =
+      Lexer::getAsCharRange(D->getSourceRange(), SrcMgr, Ctx.getLangOpts());
+  if (R.isInvalid())
+    return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                   "invalid source range for using "
+                                   "directive");
+  SourceLocation EndLoc = R.getEnd();
+  auto NextTok = Lexer::findNextToken(EndLoc, SrcMgr, Ctx.getLangOpts());
+  if (!NextTok || NextTok->getKind() != tok::semi)
+    return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                   "no semicolon after using-directive");
+  R.setEnd(NextTok->getEndLoc());
+  // FIXME: removing the semicolon may be invalid in some obscure cases, e.g.
+  //        if (x) using namespace std; else using namespace bar;
+  return tooling::Replacement(SrcMgr, R, "", Ctx.getLangOpts());
+}
+} // namespace
+  // namespace
+
+llvm::Optional<PreparedAction>
+RemoveUsingNamespace::prepare(const ActionInputs &Inputs) {
+  auto &Ctx = Inputs.AST.getASTContext();
+  // Find the 'using namespace' directive under the cursor.
+  UsingDirectiveDecl *TargetDirective = nullptr;
+  FindNodeUnderCursor(Inputs.Cursor, TargetDirective).TraverseAST(Ctx);
+  if (TargetDirective == nullptr)
+    return None;
+  auto *Parent = dyn_cast<Decl>(TargetDirective->getDeclContext());
+  if (!Parent)
+    return None;
+
+  // The second stage that would deal with the actual removal.
+  auto Do = [&Ctx, TargetDirective,
+             Parent]() -> llvm::Expected<tooling::Replacements> {
+    auto OldScopes = Ctx.getTraversalScope();
+    auto _ = llvm::make_scope_exit([&]() { Ctx.setTraversalScope(OldScopes); });
+    Ctx.setTraversalScope({Parent});
+
+    // First, collect *all* using namespace directives that redeclare the same
+    // namespace.
+    std::vector<UsingDirectiveDecl *> AllDirectives;
+    FindSameUsings(*TargetDirective, AllDirectives).TraverseAST(Ctx);
+    // Collect all references to symbols from the namespace for which we're
+    // removing the directive.
+    std::vector<SourceLocation> IdentsToQualify;
+    FindIdentsToQualify(Ctx, *TargetDirective->getNominatedNamespace(),
+                        IdentsToQualify)
+        .TraverseAST(Ctx);
+    // Remove duplicates.
+    llvm::sort(IdentsToQualify);
+    IdentsToQualify.erase(
+        std::unique(IdentsToQualify.begin(), IdentsToQualify.end()),
+        IdentsToQualify.end());
+
+    // Produce replacements to remove the using directives.
+    tooling::Replacements R;
+    for (auto *D : AllDirectives) {
+      auto RemoveUsing = removeUsingDirective(Ctx, D);
+      if (!RemoveUsing)
+        return RemoveUsing.takeError();
+      if (auto Err = R.add(*RemoveUsing))
+        return std::move(Err);
+    }
+    // Produce replacements to add the qualifiers.
+    std::string Qualifier =
+        printUsingNamespaceName(Ctx, *TargetDirective) + "::";
+    for (auto Loc : IdentsToQualify) {
+      if (auto Err = R.add(tooling::Replacement(Ctx.getSourceManager(), Loc,
+                                                /*Length=*/0, Qualifier)))
+        return std::move(Err);
+    }
+    return R;
+  };
+  return PreparedAction(
+      llvm::formatv("Remove using namespace, add qualifiers instead"), Do);
+}
+} // namespace clangd
+} // namespace clang
Index: clangd/CodeActions.cpp
===================================================================
--- clangd/CodeActions.cpp
+++ clangd/CodeActions.cpp
@@ -10,6 +10,7 @@
 #include "Logger.h"
 #include "refactor/actions/QualifyName.h"
 #include "refactor/actions/SwapIfBranches.h"
+#include "refactor/actions/RemoveUsingNamespace.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/FormatVariadic.h"
@@ -24,6 +25,7 @@
   // FIXME: provide a registry of the providers.
   Actions.push_back(llvm::make_unique<QualifyName>());
   Actions.push_back(llvm::make_unique<SwapIfBranches>());
+  Actions.push_back(llvm::make_unique<RemoveUsingNamespace>());
 }
 
 std::vector<PreparedAction>
Index: clangd/CMakeLists.txt
===================================================================
--- clangd/CMakeLists.txt
+++ clangd/CMakeLists.txt
@@ -64,6 +64,7 @@
   index/dex/Trigram.cpp
 
   refactor/ActionProvider.cpp
+  refactor/actions/RemoveUsingNamespace.cpp
   refactor/actions/QualifyName.cpp
   refactor/actions/SwapIfBranches.cpp
 
Index: clangd/AST.h
===================================================================
--- clangd/AST.h
+++ clangd/AST.h
@@ -42,6 +42,12 @@
 /// Returns the first enclosing namespace scope starting from \p DC.
 std::string printNamespaceScope(const DeclContext &DC);
 
+/// Returns the name of the namespace inside the 'using namespace' directive, as
+/// written in the code. E.g., passing 'using namespace ::std' will result in
+/// '::std'.
+std::string printUsingNamespaceName(const ASTContext &Ctx,
+                                    const UsingDirectiveDecl &D);
+
 /// Prints unqualified name of the decl for the purpose of displaying it to the
 /// user. Anonymous decls return names of the form "(anonymous {kind})", e.g.
 /// "(anonymous struct)" or "(anonymous namespace)".
Index: clangd/AST.cpp
===================================================================
--- clangd/AST.cpp
+++ clangd/AST.cpp
@@ -77,16 +77,25 @@
   return nullptr;
 }
 
+std::string printUsingNamespaceName(const ASTContext &Ctx,
+                                    const UsingDirectiveDecl &D) {
+  PrintingPolicy PP(Ctx.getLangOpts());
+  std::string Name;
+  llvm::raw_string_ostream Out(Name);
+
+  if (auto *Qual = D.getQualifier())
+    Qual->print(Out, PP);
+  D.getNominatedNamespaceAsWritten()->printName(Out);
+  return Out.str();
+}
+
 std::string printName(const ASTContext &Ctx, const NamedDecl &ND) {
   std::string Name;
   llvm::raw_string_ostream Out(Name);
   PrintingPolicy PP(Ctx.getLangOpts());
   // Handle 'using namespace'. They all have the same name - <using-directive>.
   if (auto *UD = llvm::dyn_cast<UsingDirectiveDecl>(&ND)) {
-    Out << "using namespace ";
-    if (auto *Qual = UD->getQualifier())
-      Qual->print(Out, PP);
-    UD->getNominatedNamespaceAsWritten()->printName(Out);
+    Out << "using namespace " << printUsingNamespaceName(Ctx, *UD);
     return Out.str();
   }
   ND.getDeclName().print(Out, PP);
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to