r296616 - Introducing clang::tooling::AtomicChange for refactoring tools.

Eric Liu via cfe-commits cfe-commits at lists.llvm.org
Wed Mar 1 05:14:01 PST 2017


Author: ioeric
Date: Wed Mar  1 07:14:01 2017
New Revision: 296616

URL: http://llvm.org/viewvc/llvm-project?rev=296616&view=rev
Log:
Introducing clang::tooling::AtomicChange for refactoring tools.

Summary:
An AtomicChange is used to create and group a set of source edits, e.g.
replacements or header insertions. Edits in an AtomicChange should be related,
e.g. replacements for the same type reference and the corresponding header
insertion/deletion.

An AtomicChange is uniquely identified by a key position and will either be
fully applied or not applied at all. The key position should be the location
of the key syntactical element that is being changed, e.g. the call to a
refactored method.

Next step: add a tool that applies AtomicChange.

Reviewers: klimek, djasper

Reviewed By: klimek

Subscribers: alexshap, cfe-commits, djasper, mgorny

Differential Revision: https://reviews.llvm.org/D27054

Added:
    cfe/trunk/include/clang/Tooling/Refactoring/
    cfe/trunk/include/clang/Tooling/Refactoring/AtomicChange.h
    cfe/trunk/lib/Tooling/Refactoring/
    cfe/trunk/lib/Tooling/Refactoring/AtomicChange.cpp
    cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt
Modified:
    cfe/trunk/lib/Tooling/CMakeLists.txt
    cfe/trunk/unittests/Tooling/CMakeLists.txt
    cfe/trunk/unittests/Tooling/RefactoringTest.cpp

Added: cfe/trunk/include/clang/Tooling/Refactoring/AtomicChange.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Refactoring/AtomicChange.h?rev=296616&view=auto
==============================================================================
--- cfe/trunk/include/clang/Tooling/Refactoring/AtomicChange.h (added)
+++ cfe/trunk/include/clang/Tooling/Refactoring/AtomicChange.h Wed Mar  1 07:14:01 2017
@@ -0,0 +1,129 @@
+//===--- AtomicChange.h - AtomicChange class --------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file defines AtomicChange which is used to create a set of source
+//  changes, e.g. replacements and header insertions.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLING_REFACTOR_ATOMICCHANGE_H
+#define LLVM_CLANG_TOOLING_REFACTOR_ATOMICCHANGE_H
+
+#include "clang/Basic/SourceManager.h"
+#include "clang/Tooling/Core/Replacement.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+namespace clang {
+namespace tooling {
+
+/// \brief An atomic change is used to create and group a set of source edits,
+/// e.g. replacements or header insertions. Edits in an AtomicChange should be
+/// related, e.g. replacements for the same type reference and the corresponding
+/// header insertion/deletion.
+///
+/// An AtomicChange is uniquely identified by a key and will either be fully
+/// applied or not applied at all.
+///
+/// Calling setError on an AtomicChange stores the error message and marks it as
+/// bad, i.e. none of its source edits will be applied.
+class AtomicChange {
+public:
+  /// \brief Creates an atomic change around \p KeyPosition with the key being a
+  /// concatenation of the file name and the offset of \p KeyPosition.
+  /// \p KeyPosition should be the location of the key syntactical element that
+  /// is being changed, e.g. the call to a refactored method.
+  AtomicChange(const SourceManager &SM, SourceLocation KeyPosition);
+
+  /// \brief Creates an atomic change for \p FilePath with a customized key.
+  AtomicChange(llvm::StringRef FilePath, llvm::StringRef Key)
+      : Key(Key), FilePath(FilePath) {}
+
+  /// \brief Returns the atomic change as a YAML string.
+  std::string toYAMLString();
+
+  /// \brief Converts a YAML-encoded automic change to AtomicChange.
+  static AtomicChange convertFromYAML(llvm::StringRef YAMLContent);
+
+  /// \brief Returns the key of this change, which is a concatenation of the
+  /// file name and offset of the key position.
+  const std::string &getKey() const { return Key; }
+
+  /// \brief Returns the path of the file containing this atomic change.
+  const std::string &getFilePath() const { return FilePath; }
+
+  /// \brief If this change could not be created successfully, e.g. because of
+  /// conflicts among replacements, use this to set an error description.
+  /// Thereby, places that cannot be fixed automatically can be gathered when
+  /// applying changes.
+  void setError(llvm::StringRef Error) { this->Error = Error; }
+
+  /// \brief Returns whether an error has been set on this list.
+  bool hasError() const { return !Error.empty(); }
+
+  /// \brief Returns the error message or an empty string if it does not exist.
+  const std::string &getError() const { return Error; }
+
+  /// \brief Adds a replacement that replaces range [Loc, Loc+Length) with
+  /// \p Text.
+  /// \returns An llvm::Error carrying ReplacementError on error.
+  llvm::Error replace(const SourceManager &SM, SourceLocation Loc,
+                      unsigned Length, llvm::StringRef Text);
+
+  /// \brief Adds a replacement that inserts \p Text at \p Loc. If this
+  /// insertion conflicts with an existing insertion (at the same position),
+  /// this will be inserted before/after the existing insertion depending on
+  /// \p InsertAfter. Users should use `replace` with `Length=0` instead if they
+  /// do not want conflict resolving by default. If the conflicting replacement
+  /// is not an insertion, an error is returned.
+  ///
+  /// \returns An llvm::Error carrying ReplacementError on error.
+  llvm::Error insert(const SourceManager &SM, SourceLocation Loc,
+                     llvm::StringRef Text, bool InsertAfter = true);
+
+  /// \brief Adds a header into the file that contains the key position.
+  /// Header can be in angle brackets or double quotation marks. By default
+  /// (header is not quoted), header will be surrounded with double quotes.
+  void addHeader(llvm::StringRef Header);
+
+  /// \brief Removes a header from the file that contains the key position.
+  void removeHeader(llvm::StringRef Header);
+
+  /// \brief Returns a const reference to existing replacements.
+  const Replacements &getReplacements() const { return Replaces; }
+
+  llvm::ArrayRef<std::string> getInsertedHeaders() const {
+    return InsertedHeaders;
+  }
+
+  llvm::ArrayRef<std::string> getRemovedHeaders() const {
+    return RemovedHeaders;
+  }
+
+private:
+  AtomicChange() {}
+
+  AtomicChange(std::string Key, std::string FilePath, std::string Error,
+               std::vector<std::string> InsertedHeaders,
+               std::vector<std::string> RemovedHeaders,
+               clang::tooling::Replacements Replaces);
+
+  // This uniquely identifies an AtomicChange.
+  std::string Key;
+  std::string FilePath;
+  std::string Error;
+  std::vector<std::string> InsertedHeaders;
+  std::vector<std::string> RemovedHeaders;
+  tooling::Replacements Replaces;
+};
+
+} // end namespace tooling
+} // end namespace clang
+
+#endif // LLVM_CLANG_TOOLING_REFACTOR_ATOMICCHANGE_H

Modified: cfe/trunk/lib/Tooling/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/CMakeLists.txt?rev=296616&r1=296615&r2=296616&view=diff
==============================================================================
--- cfe/trunk/lib/Tooling/CMakeLists.txt (original)
+++ cfe/trunk/lib/Tooling/CMakeLists.txt Wed Mar  1 07:14:01 2017
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_subdirectory(Core)
+add_subdirectory(Refactoring)
 
 add_clang_library(clangTooling
   ArgumentsAdjusters.cpp

Added: cfe/trunk/lib/Tooling/Refactoring/AtomicChange.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Refactoring/AtomicChange.cpp?rev=296616&view=auto
==============================================================================
--- cfe/trunk/lib/Tooling/Refactoring/AtomicChange.cpp (added)
+++ cfe/trunk/lib/Tooling/Refactoring/AtomicChange.cpp Wed Mar  1 07:14:01 2017
@@ -0,0 +1,167 @@
+//===--- AtomicChange.cpp - AtomicChange implementation -----------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Tooling/Refactoring/AtomicChange.h"
+#include "clang/Tooling/ReplacementsYaml.h"
+#include "llvm/Support/YAMLTraits.h"
+#include <string>
+
+LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(std::string)
+LLVM_YAML_IS_SEQUENCE_VECTOR(clang::tooling::AtomicChange)
+
+namespace {
+/// \brief Helper to (de)serialize an AtomicChange since we don't have direct
+/// access to its data members.
+/// Data members of a normalized AtomicChange can be directly mapped from/to
+/// YAML string.
+struct NormalizedAtomicChange {
+  NormalizedAtomicChange() = default;
+
+  NormalizedAtomicChange(const llvm::yaml::IO &) {}
+
+  // This converts AtomicChange's internal implementation of the replacements
+  // set to a vector of replacements.
+  NormalizedAtomicChange(const llvm::yaml::IO &,
+                         const clang::tooling::AtomicChange &E)
+      : Key(E.getKey()), FilePath(E.getFilePath()), Error(E.getError()),
+        InsertedHeaders(E.getInsertedHeaders()),
+        RemovedHeaders(E.getRemovedHeaders()),
+        Replaces(E.getReplacements().begin(), E.getReplacements().end()) {}
+
+  // This is not expected to be called but needed for template instantiation.
+  clang::tooling::AtomicChange denormalize(const llvm::yaml::IO &) {
+    llvm_unreachable("Do not convert YAML to AtomicChange directly with '>>'. "
+                     "Use AtomicChange::convertFromYAML instead.");
+  }
+  std::string Key;
+  std::string FilePath;
+  std::string Error;
+  std::vector<std::string> InsertedHeaders;
+  std::vector<std::string> RemovedHeaders;
+  std::vector<clang::tooling::Replacement> Replaces;
+};
+} // anonymous namespace
+
+namespace llvm {
+namespace yaml {
+
+/// \brief Specialized MappingTraits to describe how an AtomicChange is
+/// (de)serialized.
+template <> struct MappingTraits<NormalizedAtomicChange> {
+  static void mapping(IO &Io, NormalizedAtomicChange &Doc) {
+    Io.mapRequired("Key", Doc.Key);
+    Io.mapRequired("FilePath", Doc.FilePath);
+    Io.mapRequired("Error", Doc.Error);
+    Io.mapRequired("InsertedHeaders", Doc.InsertedHeaders);
+    Io.mapRequired("RemovedHeaders", Doc.RemovedHeaders);
+    Io.mapRequired("Replacements", Doc.Replaces);
+  }
+};
+
+/// \brief Specialized MappingTraits to describe how an AtomicChange is
+/// (de)serialized.
+template <> struct MappingTraits<clang::tooling::AtomicChange> {
+  static void mapping(IO &Io, clang::tooling::AtomicChange &Doc) {
+    MappingNormalization<NormalizedAtomicChange, clang::tooling::AtomicChange>
+        Keys(Io, Doc);
+    Io.mapRequired("Key", Keys->Key);
+    Io.mapRequired("FilePath", Keys->FilePath);
+    Io.mapRequired("Error", Keys->Error);
+    Io.mapRequired("InsertedHeaders", Keys->InsertedHeaders);
+    Io.mapRequired("RemovedHeaders", Keys->RemovedHeaders);
+    Io.mapRequired("Replacements", Keys->Replaces);
+  }
+};
+
+} // end namespace yaml
+} // end namespace llvm
+
+namespace clang {
+namespace tooling {
+
+AtomicChange::AtomicChange(const SourceManager &SM,
+                           SourceLocation KeyPosition) {
+  const FullSourceLoc FullKeyPosition(KeyPosition, SM);
+  std::pair<FileID, unsigned> FileIDAndOffset =
+      FullKeyPosition.getSpellingLoc().getDecomposedLoc();
+  const FileEntry *FE = SM.getFileEntryForID(FileIDAndOffset.first);
+  assert(FE && "Cannot create AtomicChange with invalid location.");
+  FilePath = FE->getName();
+  Key = FilePath + ":" + std::to_string(FileIDAndOffset.second);
+}
+
+AtomicChange::AtomicChange(std::string Key, std::string FilePath,
+                           std::string Error,
+                           std::vector<std::string> InsertedHeaders,
+                           std::vector<std::string> RemovedHeaders,
+                           clang::tooling::Replacements Replaces)
+    : Key(std::move(Key)), FilePath(std::move(FilePath)),
+      Error(std::move(Error)), InsertedHeaders(std::move(InsertedHeaders)),
+      RemovedHeaders(std::move(RemovedHeaders)), Replaces(std::move(Replaces)) {
+}
+
+std::string AtomicChange::toYAMLString() {
+  std::string YamlContent;
+  llvm::raw_string_ostream YamlContentStream(YamlContent);
+
+  llvm::yaml::Output YAML(YamlContentStream);
+  YAML << *this;
+  YamlContentStream.flush();
+  return YamlContent;
+}
+
+AtomicChange AtomicChange::convertFromYAML(llvm::StringRef YAMLContent) {
+  NormalizedAtomicChange NE;
+  llvm::yaml::Input YAML(YAMLContent);
+  YAML >> NE;
+  AtomicChange E(NE.Key, NE.FilePath, NE.Error, NE.InsertedHeaders,
+                 NE.RemovedHeaders, tooling::Replacements());
+  for (const auto &R : NE.Replaces) {
+    llvm::Error Err = E.Replaces.add(R);
+    if (Err)
+      llvm_unreachable(
+          "Failed to add replacement when Converting YAML to AtomicChange.");
+    llvm::consumeError(std::move(Err));
+  }
+  return E;
+}
+
+llvm::Error AtomicChange::insert(const SourceManager &SM, SourceLocation Loc,
+                                 llvm::StringRef Text, bool InsertAfter) {
+  if (Text.empty())
+    return llvm::Error::success();
+  Replacement R(SM, Loc, 0, Text);
+  llvm::Error Err = Replaces.add(R);
+  if (Err) {
+    return llvm::handleErrors(
+        std::move(Err), [&](const ReplacementError &RE) -> llvm::Error {
+          if (RE.get() != replacement_error::insert_conflict)
+            return llvm::make_error<ReplacementError>(RE);
+          unsigned NewOffset = Replaces.getShiftedCodePosition(R.getOffset());
+          if (!InsertAfter)
+            NewOffset -=
+                RE.getExistingReplacement()->getReplacementText().size();
+          Replacement NewR(R.getFilePath(), NewOffset, 0, Text);
+          Replaces = Replaces.merge(Replacements(NewR));
+          return llvm::Error::success();
+        });
+  }
+  return llvm::Error::success();
+}
+
+void AtomicChange::addHeader(llvm::StringRef Header) {
+  InsertedHeaders.push_back(Header);
+}
+
+void AtomicChange::removeHeader(llvm::StringRef Header) {
+  RemovedHeaders.push_back(Header);
+}
+
+} // end namespace tooling
+} // end namespace clang

Added: cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt?rev=296616&view=auto
==============================================================================
--- cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt (added)
+++ cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt Wed Mar  1 07:14:01 2017
@@ -0,0 +1,12 @@
+set(LLVM_LINK_COMPONENTS
+  Option
+  Support
+  )
+
+add_clang_library(clangToolingRefactor
+  AtomicChange.cpp
+
+  LINK_LIBS
+  clangBasic
+  clangToolingCore
+  )

Modified: cfe/trunk/unittests/Tooling/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/Tooling/CMakeLists.txt?rev=296616&r1=296615&r2=296616&view=diff
==============================================================================
--- cfe/trunk/unittests/Tooling/CMakeLists.txt (original)
+++ cfe/trunk/unittests/Tooling/CMakeLists.txt Wed Mar  1 07:14:01 2017
@@ -13,7 +13,7 @@ endif()
 add_clang_unittest(ToolingTests
   CommentHandlerTest.cpp
   CompilationDatabaseTest.cpp
-  FixItTest.cpp  
+  FixItTest.cpp
   LookupTest.cpp
   QualTypeNamesTest.cpp
   RecursiveASTVisitorTest.cpp
@@ -38,4 +38,5 @@ target_link_libraries(ToolingTests
   clangRewrite
   clangTooling
   clangToolingCore
+  clangToolingRefactor
   )

Modified: cfe/trunk/unittests/Tooling/RefactoringTest.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/Tooling/RefactoringTest.cpp?rev=296616&r1=296615&r2=296616&view=diff
==============================================================================
--- cfe/trunk/unittests/Tooling/RefactoringTest.cpp (original)
+++ cfe/trunk/unittests/Tooling/RefactoringTest.cpp Wed Mar  1 07:14:01 2017
@@ -26,6 +26,7 @@
 #include "clang/Frontend/TextDiagnosticPrinter.h"
 #include "clang/Rewrite/Core/Rewriter.h"
 #include "clang/Tooling/Refactoring.h"
+#include "clang/Tooling/Refactoring/AtomicChange.h"
 #include "clang/Tooling/Tooling.h"
 #include "llvm/ADT/SmallString.h"
 #include "gtest/gtest.h"
@@ -102,10 +103,10 @@ TEST_F(ReplacementTest, ReturnsInvalidPa
 
 // Checks that an llvm::Error instance contains a ReplacementError with expected
 // error code, expected new replacement, and expected existing replacement.
-static bool checkReplacementError(
-    llvm::Error&& Error, replacement_error ExpectedErr,
-    llvm::Optional<Replacement> ExpectedExisting,
-    llvm::Optional<Replacement> ExpectedNew) {
+static bool checkReplacementError(llvm::Error &&Error,
+                                  replacement_error ExpectedErr,
+                                  llvm::Optional<Replacement> ExpectedExisting,
+                                  llvm::Optional<Replacement> ExpectedNew) {
   if (!Error) {
     llvm::errs() << "Error is a success.";
     return false;
@@ -1089,5 +1090,187 @@ TEST(DeduplicateByFileTest, NonExistingF
   EXPECT_TRUE(FileToReplaces.empty());
 }
 
+class AtomicChangeTest : public ::testing::Test {
+  protected:
+    void setUp() {
+      DefaultFileID = Context.createInMemoryFile("input.cpp", DefaultCode);
+      DefaultLoc = Context.Sources.getLocForStartOfFile(DefaultFileID)
+                       .getLocWithOffset(20);
+      assert(DefaultLoc.isValid() && "Default location must be valid.");
+    }
+
+    RewriterTestContext Context;
+    std::string DefaultCode = std::string(100, 'a');
+    unsigned DefaultOffset = 20;
+    SourceLocation DefaultLoc;
+    FileID DefaultFileID;
+};
+
+TEST_F(AtomicChangeTest, AtomicChangeToYAML) {
+  setUp();
+  AtomicChange Change(Context.Sources, DefaultLoc);
+  llvm::Error Err =
+      Change.insert(Context.Sources, DefaultLoc, "aa", /*InsertAfter=*/false);
+  ASSERT_TRUE(!Err);
+  Err = Change.insert(Context.Sources, DefaultLoc.getLocWithOffset(10), "bb",
+                    /*InsertAfter=*/false);
+  ASSERT_TRUE(!Err);
+  Change.addHeader("a.h");
+  Change.removeHeader("b.h");
+  std::string YAMLString = Change.toYAMLString();
+
+  // NOTE: If this test starts to fail for no obvious reason, check whitespace.
+  ASSERT_STREQ("---\n"
+               "Key:             'input.cpp:20'\n"
+               "FilePath:        input.cpp\n"
+               "Error:           ''\n"
+               "InsertedHeaders: [ a.h ]\n"
+               "RemovedHeaders:  [ b.h ]\n"
+               "Replacements:    \n" // Extra whitespace here!
+               "  - FilePath:        input.cpp\n"
+               "    Offset:          20\n"
+               "    Length:          0\n"
+               "    ReplacementText: aa\n"
+               "  - FilePath:        input.cpp\n"
+               "    Offset:          30\n"
+               "    Length:          0\n"
+               "    ReplacementText: bb\n"
+               "...\n",
+               YAMLString.c_str());
+}
+
+TEST_F(AtomicChangeTest, YAMLToAtomicChange) {
+  setUp();
+  std::string YamlContent = "---\n"
+                            "Key:             'input.cpp:20'\n"
+                            "FilePath:        input.cpp\n"
+                            "Error:           'ok'\n"
+                            "InsertedHeaders: [ a.h ]\n"
+                            "RemovedHeaders:  [ b.h ]\n"
+                            "Replacements:    \n" // Extra whitespace here!
+                            "  - FilePath:        input.cpp\n"
+                            "    Offset:          20\n"
+                            "    Length:          0\n"
+                            "    ReplacementText: aa\n"
+                            "  - FilePath:        input.cpp\n"
+                            "    Offset:          30\n"
+                            "    Length:          0\n"
+                            "    ReplacementText: bb\n"
+                            "...\n";
+  AtomicChange ExpectedChange(Context.Sources, DefaultLoc);
+  llvm::Error Err = ExpectedChange.insert(Context.Sources, DefaultLoc, "aa",
+                                        /*InsertAfter=*/false);
+  ASSERT_TRUE(!Err);
+  Err = ExpectedChange.insert(Context.Sources, DefaultLoc.getLocWithOffset(10),
+                            "bb", /*InsertAfter=*/false);
+  ASSERT_TRUE(!Err);
+
+  ExpectedChange.addHeader("a.h");
+  ExpectedChange.removeHeader("b.h");
+  ExpectedChange.setError("ok");
+
+  AtomicChange ActualChange = AtomicChange::convertFromYAML(YamlContent);
+  EXPECT_EQ(ExpectedChange.getKey(), ActualChange.getKey());
+  EXPECT_EQ(ExpectedChange.getFilePath(), ActualChange.getFilePath());
+  EXPECT_EQ(ExpectedChange.getError(), ActualChange.getError());
+  EXPECT_EQ(ExpectedChange.getInsertedHeaders(), ActualChange.getInsertedHeaders());
+  EXPECT_EQ(ExpectedChange.getRemovedHeaders(), ActualChange.getRemovedHeaders());
+  EXPECT_EQ(ExpectedChange.getReplacements().size(),
+            ActualChange.getReplacements().size());
+  EXPECT_EQ(2u, ActualChange.getReplacements().size());
+  EXPECT_EQ(*ExpectedChange.getReplacements().begin(),
+            *ActualChange.getReplacements().begin());
+  EXPECT_EQ(*(++ExpectedChange.getReplacements().begin()),
+            *(++ActualChange.getReplacements().begin()));
+}
+
+TEST_F(AtomicChangeTest, CheckKeyAndKeyFile) {
+  setUp();
+  AtomicChange Change(Context.Sources, DefaultLoc);
+  EXPECT_EQ("input.cpp:20", Change.getKey());
+  EXPECT_EQ("input.cpp", Change.getFilePath());
+}
+
+TEST_F(AtomicChangeTest, InsertBefore) {
+  setUp();
+  AtomicChange Change(Context.Sources, DefaultLoc);
+  llvm::Error Err = Change.insert(Context.Sources, DefaultLoc, "aa");
+  ASSERT_TRUE(!Err);
+  EXPECT_EQ(Change.getReplacements().size(), 1u);
+  EXPECT_EQ(*Change.getReplacements().begin(),
+            Replacement(Context.Sources, DefaultLoc, 0, "aa"));
+  Err = Change.insert(Context.Sources, DefaultLoc, "b", /*InsertAfter=*/false);
+  ASSERT_TRUE(!Err);
+  EXPECT_EQ(Change.getReplacements().size(), 1u);
+  EXPECT_EQ(*Change.getReplacements().begin(),
+            Replacement(Context.Sources, DefaultLoc, 0, "baa"));
+}
+
+TEST_F(AtomicChangeTest, InsertAfter) {
+  setUp();
+  AtomicChange Change(Context.Sources, DefaultLoc);
+  llvm::Error Err = Change.insert(Context.Sources, DefaultLoc, "aa");
+  ASSERT_TRUE(!Err);
+  EXPECT_EQ(Change.getReplacements().size(), 1u);
+  EXPECT_EQ(*Change.getReplacements().begin(),
+            Replacement(Context.Sources, DefaultLoc, 0, "aa"));
+  Err = Change.insert(Context.Sources, DefaultLoc, "b");
+  ASSERT_TRUE(!Err);
+  EXPECT_EQ(Change.getReplacements().size(), 1u);
+  EXPECT_EQ(*Change.getReplacements().begin(),
+            Replacement(Context.Sources, DefaultLoc, 0, "aab"));
+}
+
+TEST_F(AtomicChangeTest, InsertBeforeWithInvalidLocation) {
+  setUp();
+  AtomicChange Change(Context.Sources, DefaultLoc);
+  llvm::Error Err =
+      Change.insert(Context.Sources, DefaultLoc, "a", /*InsertAfter=*/false);
+  ASSERT_TRUE(!Err);
+
+  // Invalid location.
+  Err = Change.insert(Context.Sources, SourceLocation(), "a",
+                    /*InsertAfter=*/false);
+  ASSERT_TRUE((bool)Err);
+  EXPECT_TRUE(checkReplacementError(
+      std::move(Err), replacement_error::wrong_file_path,
+      Replacement(Context.Sources, DefaultLoc, 0, "a"),
+      Replacement(Context.Sources, SourceLocation(), 0, "a")));
+
+}
+
+TEST_F(AtomicChangeTest, InsertBeforeToWrongFile) {
+  setUp();
+  AtomicChange Change(Context.Sources, DefaultLoc);
+  llvm::Error Err =
+      Change.insert(Context.Sources, DefaultLoc, "a", /*InsertAfter=*/false);
+  ASSERT_TRUE(!Err);
+
+  // Inserting at a different file.
+  FileID NewID = Context.createInMemoryFile("extra.cpp", DefaultCode);
+  SourceLocation NewLoc = Context.Sources.getLocForStartOfFile(NewID);
+  Err = Change.insert(Context.Sources, NewLoc, "b", /*InsertAfter=*/false);
+  ASSERT_TRUE((bool)Err);
+  EXPECT_TRUE(
+      checkReplacementError(std::move(Err), replacement_error::wrong_file_path,
+                            Replacement(Context.Sources, DefaultLoc, 0, "a"),
+                            Replacement(Context.Sources, NewLoc, 0, "b")));
+}
+
+TEST_F(AtomicChangeTest, InsertAfterWithInvalidLocation) {
+  setUp();
+  AtomicChange Change(Context.Sources, DefaultLoc);
+  llvm::Error Err = Change.insert(Context.Sources, DefaultLoc, "a");
+  ASSERT_TRUE(!Err);
+
+  // Invalid location.
+  Err = Change.insert(Context.Sources, SourceLocation(), "b");
+  ASSERT_TRUE((bool)Err);
+  EXPECT_TRUE(checkReplacementError(
+      std::move(Err), replacement_error::wrong_file_path,
+      Replacement(Context.Sources, DefaultLoc, 0, "a"),
+      Replacement(Context.Sources, SourceLocation(), 0, "b")));
+}
+
 } // end namespace tooling
 } // end namespace clang




More information about the cfe-commits mailing list