ilya-biryukov updated this revision to Diff 182900.
ilya-biryukov added a comment.

- Use a helper to avoid creating RewriteBuffer


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D56611/new/

https://reviews.llvm.org/D56611

Files:
  clang-tools-extra/clangd/SourceCode.cpp
  clang-tools-extra/clangd/SourceCode.h
  clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt
  clang-tools-extra/clangd/refactor/tweaks/SwapIfBranches.cpp
  clang-tools-extra/unittests/clangd/CMakeLists.txt
  clang-tools-extra/unittests/clangd/TweakTests.cpp

Index: clang-tools-extra/unittests/clangd/TweakTests.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/unittests/clangd/TweakTests.cpp
@@ -0,0 +1,145 @@
+//===-- TweakTests.cpp ------------------------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "Annotations.h"
+#include "SourceCode.h"
+#include "TestTU.h"
+#include "refactor/Tweak.h"
+#include "clang/AST/Expr.h"
+#include "clang/Rewrite/Core/Rewriter.h"
+#include "clang/Tooling/Core/Replacement.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <cassert>
+
+using llvm::Failed;
+using llvm::HasValue;
+using llvm::Succeeded;
+using ::testing::IsEmpty;
+using ::testing::Not;
+
+namespace clang {
+namespace clangd {
+namespace {
+
+std::string markRange(llvm::StringRef Code, Range R) {
+  size_t Begin = llvm::cantFail(positionToOffset(Code, R.start));
+  size_t End = llvm::cantFail(positionToOffset(Code, R.end));
+  assert(Begin <= End);
+  if (Begin == End) // Mark a single point.
+    return (Code.substr(0, Begin) + "^" + Code.substr(Begin)).str();
+  // Mark a range.
+  return (Code.substr(0, Begin) + "[[" + Code.substr(Begin, End - Begin) +
+          "]]" + Code.substr(End))
+      .str();
+}
+
+class TweakTest : public ::testing::Test {
+public:
+  /// Checks action is available at every point and range marked in \p Input.
+  void checkAvailable(TweakID ID, llvm::StringRef Input) {
+    return checkAvailable(ID, Input, /*Available=*/true);
+  }
+
+  /// Same as checkAvailable, but checks the action is not available.
+  void checkNotAvailable(TweakID ID, llvm::StringRef Input) {
+    return checkAvailable(ID, Input, /*Available=*/false);
+  }
+
+  llvm::Expected<std::string> apply(TweakID ID, llvm::StringRef Input) {
+    Annotations Code(Input);
+    Range SelectionRng;
+    if (Code.points().size() != 0) {
+      assert(Code.ranges().size() == 0 &&
+             "both a cursor point and a selection range were specified");
+      SelectionRng = Range{Code.point(), Code.point()};
+    } else {
+      SelectionRng = Code.range();
+    }
+    TestTU TU;
+    TU.Filename = "foo.cpp";
+    TU.Code = Code.code();
+
+    ParsedAST AST = TU.build();
+    auto CursorLoc = llvm::cantFail(sourceLocationInMainFile(
+        AST.getASTContext().getSourceManager(), SelectionRng.start));
+    Tweak::Selection S = {Code.code(), AST, CursorLoc};
+
+    auto T = prepareTweak(ID, S);
+    if (!T)
+      return T.takeError();
+    auto Replacements = (*T)->apply(S);
+    if (!Replacements)
+      return Replacements.takeError();
+    return applyAllReplacements(Code.code(), *Replacements);
+  }
+
+private:
+  void checkAvailable(TweakID ID, llvm::StringRef Input, bool Available) {
+    Annotations Code(Input);
+    ASSERT_TRUE(0 < Code.points().size() || 0 < Code.ranges().size())
+        << "no points of interest specified";
+    TestTU TU;
+    TU.Filename = "foo.cpp";
+    TU.Code = Code.code();
+
+    ParsedAST AST = TU.build();
+
+    auto CheckOver = [&](Range Selection) {
+      auto CursorLoc = llvm::cantFail(sourceLocationInMainFile(
+          AST.getASTContext().getSourceManager(), Selection.start));
+      auto T = prepareTweak(ID, Tweak::Selection{Code.code(), AST, CursorLoc});
+      if (Available)
+        EXPECT_THAT_EXPECTED(T, Succeeded())
+            << "code is " << markRange(Code.code(), Selection);
+      else
+        EXPECT_THAT_EXPECTED(T, Failed())
+            << "code is " << markRange(Code.code(), Selection);
+    };
+    for (auto P : Code.points())
+      CheckOver(Range{P, P});
+    for (auto R : Code.ranges())
+      CheckOver(R);
+  }
+};
+
+TEST_F(TweakTest, SwapIfBranches) {
+  llvm::StringLiteral ID = "SwapIfBranches";
+
+  checkAvailable(ID, R"cpp(
+    void test() {
+      ^i^f (true) { return 100; } else { continue; }
+    }
+  )cpp");
+
+  checkNotAvailable(ID, R"cpp(
+    void test() {
+      if ^(^true) { return 100; } else { continue; }
+    }
+  )cpp");
+
+  llvm::StringLiteral Input = R"cpp(
+    void test() {
+      ^if (true) { return 100; } else { continue; }
+    }
+  )cpp";
+  llvm::StringLiteral Output = R"cpp(
+    void test() {
+      if (true) { continue; } else { return 100; }
+    }
+  )cpp";
+  EXPECT_THAT_EXPECTED(apply(ID, Input), HasValue(Output));
+}
+
+} // namespace
+} // namespace clangd
+} // namespace clang
Index: clang-tools-extra/unittests/clangd/CMakeLists.txt
===================================================================
--- clang-tools-extra/unittests/clangd/CMakeLists.txt
+++ clang-tools-extra/unittests/clangd/CMakeLists.txt
@@ -44,8 +44,11 @@
   TestTU.cpp
   ThreadingTests.cpp
   TraceTests.cpp
+  TweakTests.cpp
   URITests.cpp
   XRefsTests.cpp
+
+  $<TARGET_OBJECTS:obj.clangDaemonTweaks>
   )
 
 target_link_libraries(ClangdTests
Index: clang-tools-extra/clangd/refactor/tweaks/SwapIfBranches.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clangd/refactor/tweaks/SwapIfBranches.cpp
@@ -0,0 +1,118 @@
+//===--- SwapIfBranches.cpp --------------------------------------*- C++-*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#include "ClangdUnit.h"
+#include "Logger.h"
+#include "SourceCode.h"
+#include "refactor/Tweak.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/Stmt.h"
+#include "clang/Basic/LangOptions.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/Core/Replacement.h"
+#include "llvm/ADT/None.h"
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Casting.h"
+#include "llvm/Support/Error.h"
+
+namespace clang {
+namespace clangd {
+namespace {
+/// Swaps the 'then' and 'else' branch of the if statement.
+/// Before:
+///   if (foo) { return 10; } else { continue; }
+/// After:
+///   if (foo) { continue; } else { return 10; }
+class SwapIfBranches : public Tweak {
+public:
+  TweakID id() const override final;
+
+  bool prepare(const Selection &Inputs) override;
+  Expected<tooling::Replacements> apply(const Selection &Inputs) override;
+  std::string title() const override;
+
+private:
+  tooling::Replacements Result;
+};
+
+REGISTER_TWEAK(SwapIfBranches);
+
+class FindIfUnderCursor : public RecursiveASTVisitor<FindIfUnderCursor> {
+public:
+  FindIfUnderCursor(ASTContext &Ctx, SourceLocation CursorLoc, IfStmt *&Result)
+      : Ctx(Ctx), CursorLoc(CursorLoc), Result(Result) {}
+
+  bool VisitIfStmt(IfStmt *If) {
+    auto R = toHalfOpenFileRange(Ctx.getSourceManager(), Ctx.getLangOpts(),
+                                 SourceRange(If->getIfLoc()));
+    if (!R)
+      return true;
+    if (!halfOpenRangeContains(Ctx.getSourceManager(), *R, CursorLoc))
+      return true;
+    Result = If;
+    return false;
+  }
+
+private:
+  ASTContext &Ctx;
+  SourceLocation CursorLoc;
+  IfStmt *&Result;
+};
+} // namespace
+
+bool SwapIfBranches::prepare(const Selection &Inputs) {
+
+  auto &Ctx = Inputs.AST.getASTContext();
+  auto &SrcMgr = Ctx.getSourceManager();
+  IfStmt *If = nullptr;
+  FindIfUnderCursor(Ctx, Inputs.Cursor, If).TraverseAST(Ctx);
+  if (!If)
+    return false;
+
+  // avoid dealing with single-statement brances, they require careful handling
+  // to avoid changing semantics of the code (i.e. dangling else).
+  if (!llvm::dyn_cast_or_null<CompoundStmt>(If->getThen()) ||
+      !llvm::dyn_cast_or_null<CompoundStmt>(If->getElse()))
+    return false;
+
+  auto ThenRng = toHalfOpenFileRange(SrcMgr, Ctx.getLangOpts(),
+                                     If->getThen()->getSourceRange());
+  if (!ThenRng)
+    return false;
+  auto ElseRng = toHalfOpenFileRange(SrcMgr, Ctx.getLangOpts(),
+                                     If->getElse()->getSourceRange());
+  if (!ElseRng)
+    return false;
+
+  llvm::StringRef ThenCode = toSourceCode(SrcMgr, *ThenRng);
+  llvm::StringRef ElseCode = toSourceCode(SrcMgr, *ElseRng);
+
+  if (auto Err = Result.add(tooling::Replacement(SrcMgr, ThenRng->getBegin(),
+                                                 ThenCode.size(), ElseCode))) {
+    llvm::consumeError(std::move(Err));
+    return false;
+  }
+  if (auto Err = Result.add(tooling::Replacement(SrcMgr, ElseRng->getBegin(),
+                                                 ElseCode.size(), ThenCode))) {
+    llvm::consumeError(std::move(Err));
+    return false;
+  }
+  return true;
+}
+
+Expected<tooling::Replacements> SwapIfBranches::apply(const Selection &Inputs) {
+  return Result;
+}
+
+std::string SwapIfBranches::title() const { return "Swap if branches"; }
+} // namespace clangd
+} // namespace clang
Index: clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt
===================================================================
--- clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt
+++ clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt
@@ -9,4 +9,5 @@
 # clangd/tool/CMakeLists.txt for an example.
 add_clang_library(clangDaemonTweaks OBJECT
   QualifyName.cpp
+  SwapIfBranches.cpp
   )
Index: clang-tools-extra/clangd/SourceCode.h
===================================================================
--- clang-tools-extra/clangd/SourceCode.h
+++ clang-tools-extra/clangd/SourceCode.h
@@ -14,6 +14,7 @@
 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SOURCECODE_H
 #include "Protocol.h"
 #include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/LangOptions.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Tooling/Core/Replacement.h"
@@ -59,6 +60,30 @@
 llvm::Expected<SourceLocation> sourceLocationInMainFile(const SourceManager &SM,
                                                         Position P);
 
+/// Turns a token range into a half-open range and checks its correctness.
+/// The resulting range will have only valid source location on both sides, both
+/// of which are file locations.
+llvm::Optional<SourceRange> toHalfOpenFileRange(const SourceManager &Mgr,
+                                                const LangOptions &LangOpts,
+                                                SourceRange R);
+
+/// Returns true iff all of the following conditions hold:
+///   - start and end locations are valid,
+///   - start and end locations are file locations from the same file
+///     (i.e. expansion locations are not taken into account).
+///   - start offset <= end offset.
+/// FIXME: introduce a type for source range with this invariant.
+bool isValidFileRange(const SourceManager &Mgr, SourceRange R);
+
+/// Returns true iff \p L is contained in \p R.
+/// EXPECTS: isValidFileRange(R) == true, L is a file location.
+bool halfOpenRangeContains(const SourceManager &Mgr, SourceRange R,
+                           SourceLocation L);
+
+/// Returns the source code covered by the source range.
+/// EXPECTS: isValidRange(R) == true.
+llvm::StringRef toSourceCode(const SourceManager &SM, SourceRange R);
+
 // Converts a half-open clang source range to an LSP range.
 // Note that clang also uses closed source ranges, which this can't handle!
 Range halfOpenToRange(const SourceManager &SM, CharSourceRange R);
Index: clang-tools-extra/clangd/SourceCode.cpp
===================================================================
--- clang-tools-extra/clangd/SourceCode.cpp
+++ clang-tools-extra/clangd/SourceCode.cpp
@@ -11,6 +11,8 @@
 #include "clang/AST/ASTContext.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Lex/Lexer.h"
+#include "llvm/ADT/None.h"
+#include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Errc.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/Path.h"
@@ -141,6 +143,64 @@
   return P;
 }
 
+bool isValidFileRange(const SourceManager &Mgr, SourceRange R) {
+  if (!R.getBegin().isValid() || !R.getEnd().isValid())
+    return false;
+
+  FileID BeginFID;
+  size_t BeginOffset = 0;
+  std::tie(BeginFID, BeginOffset) = Mgr.getDecomposedLoc(R.getBegin());
+
+  FileID EndFID;
+  size_t EndOffset = 0;
+  std::tie(EndFID, EndOffset) = Mgr.getDecomposedLoc(R.getEnd());
+
+  return BeginFID.isValid() && BeginFID == EndFID && BeginOffset <= EndOffset;
+}
+
+bool halfOpenRangeContains(const SourceManager &Mgr, SourceRange R,
+                           SourceLocation L) {
+  assert(isValidFileRange(Mgr, R));
+
+  FileID BeginFID;
+  size_t BeginOffset = 0;
+  std::tie(BeginFID, BeginOffset) = Mgr.getDecomposedLoc(R.getBegin());
+  size_t EndOffset = Mgr.getFileOffset(R.getEnd());
+
+  FileID LFid;
+  size_t LOffset;
+  std::tie(LFid, LOffset) = Mgr.getDecomposedLoc(L);
+  return BeginFID == LFid && BeginOffset <= LOffset && LOffset < EndOffset;
+}
+
+llvm::Optional<SourceRange> toHalfOpenFileRange(const SourceManager &Mgr,
+                                                const LangOptions &LangOpts,
+                                                SourceRange R) {
+  auto Begin = Mgr.getFileLoc(R.getBegin());
+  if (Begin.isInvalid())
+    return llvm::None;
+  auto End = Mgr.getFileLoc(R.getEnd());
+  if (End.isInvalid())
+    return llvm::None;
+  End = Lexer::getLocForEndOfToken(End, 0, Mgr, LangOpts);
+
+  SourceRange Result(Begin, End);
+  if (!isValidFileRange(Mgr, Result))
+    return llvm::None;
+  return Result;
+}
+
+llvm::StringRef toSourceCode(const SourceManager &SM, SourceRange R) {
+  assert(isValidFileRange(SM, R));
+  bool Invalid = false;
+  auto *Buf = SM.getBuffer(SM.getFileID(R.getBegin()), &Invalid);
+  assert(!Invalid);
+
+  size_t BeginOffset = SM.getFileOffset(R.getBegin());
+  size_t EndOffset = SM.getFileOffset(R.getEnd());
+  return Buf->getBuffer().substr(BeginOffset, EndOffset - BeginOffset);
+}
+
 llvm::Expected<SourceLocation> sourceLocationInMainFile(const SourceManager &SM,
                                                         Position P) {
   llvm::StringRef Code = SM.getBuffer(SM.getMainFileID())->getBuffer();
@@ -169,8 +229,7 @@
   return {Lines + 1, Offset - StartOfLine + 1};
 }
 
-std::pair<llvm::StringRef, llvm::StringRef>
-splitQualifiedName(llvm::StringRef QName) {
+std::pair<StringRef, StringRef> splitQualifiedName(StringRef QName) {
   size_t Pos = QName.rfind("::");
   if (Pos == llvm::StringRef::npos)
     return {llvm::StringRef(), QName};
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to