kadircet created this revision.
kadircet added a reviewer: hokein.
Herald added subscribers: cfe-commits, usaxena95, arphaman, jkorous, MaskRay, 
ilya-biryukov.
Herald added a project: clang.

Initial implementation for apply logic, replaces function body with a
semicolon in source location and copies the full function definition into target
location.

Will handle qualification of return type and function name in following patches
to keep the changes small.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D69298

Files:
  clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
  clang-tools-extra/clangd/unittests/TweakTests.cpp

Index: clang-tools-extra/clangd/unittests/TweakTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/TweakTests.cpp
+++ clang-tools-extra/clangd/unittests/TweakTests.cpp
@@ -1561,6 +1561,21 @@
     }]]]])cpp");
 }
 
+TEST_F(DefineOutlineTest, ApplyTest) {
+  FileName = "Test.hpp";
+
+  // No implementation file.
+  EXPECT_EQ(apply("void fo^o() { return; }"),
+            "fail: Couldn't find a suitable implementation file.");
+
+  llvm::StringMap<std::string> EditedFiles;
+  ExtraFiles["Test.cpp"] = "";
+  EXPECT_EQ(apply("void fo^o() { return; }", &EditedFiles), "void foo() ;");
+  EXPECT_THAT(EditedFiles,
+              testing::ElementsAre(FileWithContents(testPath("Test.cpp"),
+                                                    "void foo() { return; }")));
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
Index: clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
===================================================================
--- clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
+++ clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
@@ -9,14 +9,18 @@
 #include "HeaderSourceSwitch.h"
 #include "Path.h"
 #include "Selection.h"
+#include "SourceCode.h"
 #include "refactor/Tweak.h"
 #include "clang/AST/ASTTypeTraits.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/Stmt.h"
+#include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Driver/Types.h"
+#include "clang/Tooling/Core/Replacement.h"
 #include "llvm/ADT/Optional.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
 #include "llvm/Support/Path.h"
 
 namespace clang {
@@ -40,6 +44,29 @@
   return nullptr;
 }
 
+llvm::Optional<Path> getSourceFile(llvm::StringRef FileName,
+                                   const Tweak::Selection &Sel) {
+  if (auto Source = getCorrespondingHeaderOrSource(
+          FileName,
+          &Sel.AST.getSourceManager().getFileManager().getVirtualFileSystem()))
+    return *Source;
+  return getCorrespondingHeaderOrSource(FileName, Sel.AST, Sel.Index);
+}
+
+// Creates a modified version of function definition that can be inserted at a
+// different location. Contains both function signature and body.
+llvm::Optional<llvm::StringRef> moveFunctionDef(const FunctionDecl *FD) {
+  auto &SM = FD->getASTContext().getSourceManager();
+  auto CharRange = toHalfOpenFileRange(SM, FD->getASTContext().getLangOpts(),
+                                       FD->getSourceRange());
+  if (!CharRange)
+    return llvm::None;
+
+  // FIXME: Qualify return type.
+  // FIXME: Qualify function name depending on the target context.
+  return toSourceCode(SM, *CharRange);
+}
+
 /// Moves definition of a function/method to an appropriate implementation file.
 /// If current file is already an implementation file, tries to move the
 /// definition out-of-line.
@@ -70,7 +97,7 @@
 
   bool prepare(const Selection &Sel) override {
     const SourceManager &SM = Sel.AST.getSourceManager();
-    llvm::StringRef FileName = SM.getFilename(Sel.Cursor);
+    FileName = SM.getFilename(Sel.Cursor);
 
     // Bail out if we are not in a header file.
     // FIXME: We might want to consider moving method definitions below class
@@ -93,12 +120,68 @@
   }
 
   Expected<Effect> apply(const Selection &Sel) override {
-    return llvm::createStringError(llvm::inconvertibleErrorCode(),
-                                   "Not implemented yet");
+    auto SourceFile = getSourceFile(FileName, Sel);
+    if (!SourceFile)
+      return llvm::createStringError(
+          llvm::inconvertibleErrorCode(),
+          "Couldn't find a suitable implementation file.");
+
+    auto &FS =
+        Sel.AST.getSourceManager().getFileManager().getVirtualFileSystem();
+    auto Buffer = FS.getBufferForFile(*SourceFile);
+    // FIXME: Maybe we should consider creating the implementation file if it
+    // doesn't exist?
+    if (!Buffer)
+      return llvm::createStringError(Buffer.getError(),
+                                     Buffer.getError().message());
+    auto Contents = Buffer->get()->getBuffer();
+    auto Region =
+        getEligiblePoints(Contents, Source->getQualifiedNameAsString(),
+                          getFormatStyleForFile(*SourceFile, Contents, &FS));
+
+    assert(!Region.EligiblePoints.empty());
+    // FIXME: This selection can be made smarter by looking at the definition
+    // locations for adjacent decls to Source. Unfortunately psudeo parsing in
+    // getEligibleRegions only knows about namespace begin/end events so we
+    // can't match function start/end positions yet.
+    auto InsertionPoint = Region.EligiblePoints.back();
+    auto InsertionOffset = positionToOffset(Contents, InsertionPoint);
+    if (!InsertionOffset)
+      return InsertionOffset.takeError();
+
+    auto FuncDef = moveFunctionDef(Source);
+    if (!FuncDef)
+      return llvm::createStringError(
+          llvm::inconvertibleErrorCode(),
+          "Couldn't get full source for function definition.");
+
+    SourceManagerForFile SMFF(*SourceFile, Contents);
+    const auto &SM = SMFF.get();
+    auto InsertLoc = SM.getComposedLoc(SM.getMainFileID(), *InsertionOffset);
+    const tooling::Replacement InsertFunctionDef(SM, InsertLoc, 0, *FuncDef);
+    auto Effect =
+        Effect::mainFileEdit(SM, tooling::Replacements(InsertFunctionDef));
+    if (!Effect)
+      return Effect.takeError();
+
+    const tooling::Replacement DeleteFuncBody(
+        Sel.AST.getSourceManager(),
+        CharSourceRange(Source->getBody()->getSourceRange(), /*ITR=*/true),
+        ";");
+    auto HeaderFE = Effect::fileEdit(Sel.AST.getSourceManager(),
+                                     Sel.AST.getSourceManager().getMainFileID(),
+                                     tooling::Replacements(DeleteFuncBody));
+    if (!HeaderFE)
+      return HeaderFE.takeError();
+
+    Effect->ApplyEdits.try_emplace(HeaderFE->first,
+                                   std::move(HeaderFE->second));
+    return std::move(*Effect);
   }
 
 private:
   const FunctionDecl *Source = nullptr;
+  llvm::StringRef FileName;
 };
 
 REGISTER_TWEAK(DefineOutline);
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to