summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--clang/include/clang/Tooling/Refactoring.h10
-rw-r--r--clang/lib/Tooling/Refactoring.cpp44
-rw-r--r--clang/unittests/Tooling/RefactoringTest.cpp71
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
OpenPOWER on IntegriCloud