diff options
-rw-r--r-- | clang/include/clang/Tooling/Refactoring.h | 10 | ||||
-rw-r--r-- | clang/lib/Tooling/Refactoring.cpp | 44 | ||||
-rw-r--r-- | clang/unittests/Tooling/RefactoringTest.cpp | 71 |
3 files changed, 125 insertions, 0 deletions
diff --git a/clang/include/clang/Tooling/Refactoring.h b/clang/include/clang/Tooling/Refactoring.h index 3b171058367..a976146a778 100644 --- a/clang/include/clang/Tooling/Refactoring.h +++ b/clang/include/clang/Tooling/Refactoring.h @@ -122,6 +122,8 @@ public: bool operator()(const Replacement &R1, const Replacement &R2) const; }; + bool operator==(const Replacement &Other) const; + private: void setFromSourceLocation(SourceManager &Sources, SourceLocation Start, unsigned Length, StringRef ReplacementText); @@ -155,6 +157,14 @@ std::string applyAllReplacements(StringRef Code, const Replacements &Replaces); /// applied. unsigned shiftedCodePosition(const Replacements& Replaces, unsigned Position); +/// \brief Removes duplicate Replacements and reports if Replacements conflict +/// with one another. +/// +/// This function will sort \p Replaces so that conflicts can be reported simply +/// by offset into \p Replaces and number of elements in the conflict. +void deduplicate(std::vector<Replacement> &Replaces, + std::vector<Range> &Conflicts); + /// \brief A tool to run refactorings. /// /// This is a refactoring specific version of \see ClangTool. FrontendActions diff --git a/clang/lib/Tooling/Refactoring.cpp b/clang/lib/Tooling/Refactoring.cpp index a61bf9aa34c..8599f975120 100644 --- a/clang/lib/Tooling/Refactoring.cpp +++ b/clang/lib/Tooling/Refactoring.cpp @@ -90,6 +90,12 @@ bool Replacement::Less::operator()(const Replacement &R1, return R1.ReplacementText < R2.ReplacementText; } +bool Replacement::operator==(const Replacement &Other) const { + return ReplacementRange.getOffset() == Other.ReplacementRange.getOffset() && + ReplacementRange.getLength() == Other.ReplacementRange.getLength() && + FilePath == Other.FilePath && ReplacementText == Other.ReplacementText; +} + void Replacement::setFromSourceLocation(SourceManager &Sources, SourceLocation Start, unsigned Length, StringRef ReplacementText) { @@ -179,6 +185,44 @@ unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position) { return NewPosition; } +void deduplicate(std::vector<Replacement> &Replaces, + std::vector<Range> &Conflicts) { + if (Replaces.empty()) + return; + + // Deduplicate + std::sort(Replaces.begin(), Replaces.end(), Replacement::Less()); + std::vector<Replacement>::iterator End = + std::unique(Replaces.begin(), Replaces.end()); + Replaces.erase(End, Replaces.end()); + + // Detect conflicts + Range ConflictRange(Replaces.front().getOffset(), + Replaces.front().getLength()); + unsigned ConflictStart = 0; + unsigned ConflictLength = 1; + for (unsigned i = 1; i < Replaces.size(); ++i) { + Range Current(Replaces[i].getOffset(), Replaces[i].getLength()); + if (ConflictRange.overlapsWith(Current)) { + // Extend conflicted range + ConflictRange = Range(ConflictRange.getOffset(), + Current.getOffset() + Current.getLength() - + ConflictRange.getOffset()); + ++ConflictLength; + } else { + if (ConflictLength > 1) + Conflicts.push_back(Range(ConflictStart, ConflictLength)); + ConflictRange = Current; + ConflictStart = i; + ConflictLength = 1; + } + } + + if (ConflictLength > 1) + Conflicts.push_back(Range(ConflictStart, ConflictLength)); +} + + RefactoringTool::RefactoringTool(const CompilationDatabase &Compilations, ArrayRef<std::string> SourcePaths) : ClangTool(Compilations, SourcePaths) {} diff --git a/clang/unittests/Tooling/RefactoringTest.cpp b/clang/unittests/Tooling/RefactoringTest.cpp index 5741e5d2227..3030162858a 100644 --- a/clang/unittests/Tooling/RefactoringTest.cpp +++ b/clang/unittests/Tooling/RefactoringTest.cpp @@ -347,5 +347,76 @@ TEST(Range, contains) { EXPECT_FALSE(Range(0, 10).contains(Range(0, 11))); } +TEST(DeduplicateTest, removesDuplicates) { + std::vector<Replacement> Input; + Input.push_back(Replacement("fileA", 50, 0, " foo ")); + Input.push_back(Replacement("fileA", 10, 3, " bar ")); + Input.push_back(Replacement("fileA", 10, 2, " bar ")); // Length differs + Input.push_back(Replacement("fileA", 9, 3, " bar ")); // Offset differs + Input.push_back(Replacement("fileA", 50, 0, " foo ")); // Duplicate + Input.push_back(Replacement("fileA", 51, 3, " bar ")); + Input.push_back(Replacement("fileB", 51, 3, " bar ")); // Filename differs! + Input.push_back(Replacement("fileA", 51, 3, " moo ")); // Replacement text + // differs! + + std::vector<Replacement> Expected; + Expected.push_back(Replacement("fileA", 9, 3, " bar ")); + Expected.push_back(Replacement("fileA", 10, 2, " bar ")); + Expected.push_back(Replacement("fileA", 10, 3, " bar ")); + Expected.push_back(Replacement("fileA", 50, 0, " foo ")); + Expected.push_back(Replacement("fileA", 51, 3, " bar ")); + Expected.push_back(Replacement("fileA", 51, 3, " moo ")); + Expected.push_back(Replacement("fileB", 51, 3, " bar ")); + + std::vector<Range> Conflicts; // Ignored for this test + deduplicate(Input, Conflicts); + + ASSERT_TRUE(Expected == Input); +} + +TEST(DeduplicateTest, detectsConflicts) { + { + std::vector<Replacement> Input; + Input.push_back(Replacement("fileA", 0, 5, " foo ")); + Input.push_back(Replacement("fileA", 0, 5, " foo ")); // Duplicate not a + // conflict. + Input.push_back(Replacement("fileA", 2, 6, " bar ")); + Input.push_back(Replacement("fileA", 7, 3, " moo ")); + + std::vector<Range> Conflicts; + deduplicate(Input, Conflicts); + + // One duplicate is removed and the remaining three items form one + // conflicted range. + ASSERT_EQ(3u, Input.size()); + ASSERT_EQ(1u, Conflicts.size()); + ASSERT_EQ(0u, Conflicts.front().getOffset()); + ASSERT_EQ(3u, Conflicts.front().getLength()); + } + { + std::vector<Replacement> Input; + + // Expected sorted order is shown. It is the sorted order to which the + // returned conflict info refers to. + Input.push_back(Replacement("fileA", 0, 5, " foo ")); // 0 + Input.push_back(Replacement("fileA", 5, 5, " bar ")); // 1 + Input.push_back(Replacement("fileA", 5, 5, " moo ")); // 2 + Input.push_back(Replacement("fileA", 15, 5, " golf ")); // 4 + Input.push_back(Replacement("fileA", 16, 5, " bag ")); // 5 + Input.push_back(Replacement("fileA", 10, 3, " club ")); // 6 + + std::vector<Range> Conflicts; + deduplicate(Input, Conflicts); + + // No duplicates + ASSERT_EQ(6u, Input.size()); + ASSERT_EQ(2u, Conflicts.size()); + ASSERT_EQ(1u, Conflicts[0].getOffset()); + ASSERT_EQ(2u, Conflicts[0].getLength()); + ASSERT_EQ(4u, Conflicts[1].getOffset()); + ASSERT_EQ(2u, Conflicts[1].getLength()); + } +} + } // end namespace tooling } // end namespace clang |