Introduce Replacement deduplication and conflict detection function

Summary:
This patch adds tooling::deduplicate() which removes duplicates from and
looks for conflicts in a vector of Replacements.

Differential Revision: http://llvm-reviews.chandlerc.com/D1314

llvm-svn: 187979
This commit is contained in:
Edwin Vane 2013-08-08 13:31:14 +00:00
parent 233455cba8
commit 938f68816a
3 changed files with 125 additions and 0 deletions

View File

@ -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

View File

@ -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) {}

View File

@ -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