llvm-project/clang/lib/Tooling/Refactoring.cpp
Benjamin Kramer efffbee024 Tooling: Ignore file names in tooling::deduplicate.
This was horribly broken due to how the sort predicate works. We would
report a conflict for files with a replacement in the same position but
different names if the length differed. Just ignore paths as this is often
what the user wants. Files can occur with different names (due to symlinks
or relative paths) and we don't ever want to do the same edit in one file
twice.

llvm-svn: 217439
2014-09-09 13:53:29 +00:00

326 lines
12 KiB
C++

//===--- Refactoring.cpp - Framework for clang refactoring tools ----------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Implements tools to support refactorings.
//
//===----------------------------------------------------------------------===//
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Lex/Lexer.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/Tooling/Refactoring.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_os_ostream.h"
namespace clang {
namespace tooling {
static const char * const InvalidLocation = "";
Replacement::Replacement()
: FilePath(InvalidLocation) {}
Replacement::Replacement(StringRef FilePath, unsigned Offset, unsigned Length,
StringRef ReplacementText)
: FilePath(FilePath), ReplacementRange(Offset, Length),
ReplacementText(ReplacementText) {}
Replacement::Replacement(const SourceManager &Sources, SourceLocation Start,
unsigned Length, StringRef ReplacementText) {
setFromSourceLocation(Sources, Start, Length, ReplacementText);
}
Replacement::Replacement(const SourceManager &Sources,
const CharSourceRange &Range,
StringRef ReplacementText) {
setFromSourceRange(Sources, Range, ReplacementText);
}
bool Replacement::isApplicable() const {
return FilePath != InvalidLocation;
}
bool Replacement::apply(Rewriter &Rewrite) const {
SourceManager &SM = Rewrite.getSourceMgr();
const FileEntry *Entry = SM.getFileManager().getFile(FilePath);
if (!Entry)
return false;
FileID ID;
// FIXME: Use SM.translateFile directly.
SourceLocation Location = SM.translateFileLineCol(Entry, 1, 1);
ID = Location.isValid() ?
SM.getFileID(Location) :
SM.createFileID(Entry, SourceLocation(), SrcMgr::C_User);
// FIXME: We cannot check whether Offset + Length is in the file, as
// the remapping API is not public in the RewriteBuffer.
const SourceLocation Start =
SM.getLocForStartOfFile(ID).
getLocWithOffset(ReplacementRange.getOffset());
// ReplaceText returns false on success.
// ReplaceText only fails if the source location is not a file location, in
// which case we already returned false earlier.
bool RewriteSucceeded = !Rewrite.ReplaceText(
Start, ReplacementRange.getLength(), ReplacementText);
assert(RewriteSucceeded);
return RewriteSucceeded;
}
std::string Replacement::toString() const {
std::string result;
llvm::raw_string_ostream stream(result);
stream << FilePath << ": " << ReplacementRange.getOffset() << ":+"
<< ReplacementRange.getLength() << ":\"" << ReplacementText << "\"";
return result;
}
bool operator<(const Replacement &LHS, const Replacement &RHS) {
if (LHS.getOffset() != RHS.getOffset())
return LHS.getOffset() < RHS.getOffset();
if (LHS.getLength() != RHS.getLength())
return LHS.getLength() < RHS.getLength();
if (LHS.getFilePath() != RHS.getFilePath())
return LHS.getFilePath() < RHS.getFilePath();
return LHS.getReplacementText() < RHS.getReplacementText();
}
bool operator==(const Replacement &LHS, const Replacement &RHS) {
return LHS.getOffset() == RHS.getOffset() &&
LHS.getLength() == RHS.getLength() &&
LHS.getFilePath() == RHS.getFilePath() &&
LHS.getReplacementText() == RHS.getReplacementText();
}
void Replacement::setFromSourceLocation(const SourceManager &Sources,
SourceLocation Start, unsigned Length,
StringRef ReplacementText) {
const std::pair<FileID, unsigned> DecomposedLocation =
Sources.getDecomposedLoc(Start);
const FileEntry *Entry = Sources.getFileEntryForID(DecomposedLocation.first);
if (Entry) {
// Make FilePath absolute so replacements can be applied correctly when
// relative paths for files are used.
llvm::SmallString<256> FilePath(Entry->getName());
std::error_code EC = llvm::sys::fs::make_absolute(FilePath);
this->FilePath = EC ? FilePath.c_str() : Entry->getName();
} else {
this->FilePath = InvalidLocation;
}
this->ReplacementRange = Range(DecomposedLocation.second, Length);
this->ReplacementText = ReplacementText;
}
// FIXME: This should go into the Lexer, but we need to figure out how
// to handle ranges for refactoring in general first - there is no obvious
// good way how to integrate this into the Lexer yet.
static int getRangeSize(const SourceManager &Sources,
const CharSourceRange &Range) {
SourceLocation SpellingBegin = Sources.getSpellingLoc(Range.getBegin());
SourceLocation SpellingEnd = Sources.getSpellingLoc(Range.getEnd());
std::pair<FileID, unsigned> Start = Sources.getDecomposedLoc(SpellingBegin);
std::pair<FileID, unsigned> End = Sources.getDecomposedLoc(SpellingEnd);
if (Start.first != End.first) return -1;
if (Range.isTokenRange())
End.second += Lexer::MeasureTokenLength(SpellingEnd, Sources,
LangOptions());
return End.second - Start.second;
}
void Replacement::setFromSourceRange(const SourceManager &Sources,
const CharSourceRange &Range,
StringRef ReplacementText) {
setFromSourceLocation(Sources, Sources.getSpellingLoc(Range.getBegin()),
getRangeSize(Sources, Range), ReplacementText);
}
bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite) {
bool Result = true;
for (Replacements::const_iterator I = Replaces.begin(),
E = Replaces.end();
I != E; ++I) {
if (I->isApplicable()) {
Result = I->apply(Rewrite) && Result;
} else {
Result = false;
}
}
return Result;
}
// FIXME: Remove this function when Replacements is implemented as std::vector
// instead of std::set.
bool applyAllReplacements(const std::vector<Replacement> &Replaces,
Rewriter &Rewrite) {
bool Result = true;
for (std::vector<Replacement>::const_iterator I = Replaces.begin(),
E = Replaces.end();
I != E; ++I) {
if (I->isApplicable()) {
Result = I->apply(Rewrite) && Result;
} else {
Result = false;
}
}
return Result;
}
std::string applyAllReplacements(StringRef Code, const Replacements &Replaces) {
FileManager Files((FileSystemOptions()));
DiagnosticsEngine Diagnostics(
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
new DiagnosticOptions);
Diagnostics.setClient(new TextDiagnosticPrinter(
llvm::outs(), &Diagnostics.getDiagnosticOptions()));
SourceManager SourceMgr(Diagnostics, Files);
Rewriter Rewrite(SourceMgr, LangOptions());
std::unique_ptr<llvm::MemoryBuffer> Buf =
llvm::MemoryBuffer::getMemBuffer(Code, "<stdin>");
const clang::FileEntry *Entry =
Files.getVirtualFile("<stdin>", Buf->getBufferSize(), 0);
SourceMgr.overrideFileContents(Entry, std::move(Buf));
FileID ID =
SourceMgr.createFileID(Entry, SourceLocation(), clang::SrcMgr::C_User);
for (Replacements::const_iterator I = Replaces.begin(), E = Replaces.end();
I != E; ++I) {
Replacement Replace("<stdin>", I->getOffset(), I->getLength(),
I->getReplacementText());
if (!Replace.apply(Rewrite))
return "";
}
std::string Result;
llvm::raw_string_ostream OS(Result);
Rewrite.getEditBuffer(ID).write(OS);
OS.flush();
return Result;
}
unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position) {
unsigned NewPosition = Position;
for (Replacements::iterator I = Replaces.begin(), E = Replaces.end(); I != E;
++I) {
if (I->getOffset() >= Position)
break;
if (I->getOffset() + I->getLength() > Position)
NewPosition += I->getOffset() + I->getLength() - Position;
NewPosition += I->getReplacementText().size() - I->getLength();
}
return NewPosition;
}
// FIXME: Remove this function when Replacements is implemented as std::vector
// instead of std::set.
unsigned shiftedCodePosition(const std::vector<Replacement> &Replaces,
unsigned Position) {
unsigned NewPosition = Position;
for (std::vector<Replacement>::const_iterator I = Replaces.begin(),
E = Replaces.end();
I != E; ++I) {
if (I->getOffset() >= Position)
break;
if (I->getOffset() + I->getLength() > Position)
NewPosition += I->getOffset() + I->getLength() - Position;
NewPosition += I->getReplacementText().size() - I->getLength();
}
return NewPosition;
}
void deduplicate(std::vector<Replacement> &Replaces,
std::vector<Range> &Conflicts) {
if (Replaces.empty())
return;
auto LessNoPath = [](const Replacement &LHS, const Replacement &RHS) {
if (LHS.getOffset() != RHS.getOffset())
return LHS.getOffset() < RHS.getOffset();
if (LHS.getLength() != RHS.getLength())
return LHS.getLength() < RHS.getLength();
return LHS.getReplacementText() < RHS.getReplacementText();
};
auto EqualNoPath = [](const Replacement &LHS, const Replacement &RHS) {
return LHS.getOffset() == RHS.getOffset() &&
LHS.getLength() == RHS.getLength() &&
LHS.getReplacementText() == RHS.getReplacementText();
};
// Deduplicate. We don't want to deduplicate based on the path as we assume
// that all replacements refer to the same file (or are symlinks).
std::sort(Replaces.begin(), Replaces.end(), LessNoPath);
Replaces.erase(std::unique(Replaces.begin(), Replaces.end(), EqualNoPath),
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(),
std::max(ConflictRange.getLength(),
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) {}
Replacements &RefactoringTool::getReplacements() { return Replace; }
int RefactoringTool::runAndSave(FrontendActionFactory *ActionFactory) {
if (int Result = run(ActionFactory)) {
return Result;
}
LangOptions DefaultLangOptions;
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts);
DiagnosticsEngine Diagnostics(
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()),
&*DiagOpts, &DiagnosticPrinter, false);
SourceManager Sources(Diagnostics, getFiles());
Rewriter Rewrite(Sources, DefaultLangOptions);
if (!applyAllReplacements(Rewrite)) {
llvm::errs() << "Skipped some replacements.\n";
}
return saveRewrittenFiles(Rewrite);
}
bool RefactoringTool::applyAllReplacements(Rewriter &Rewrite) {
return tooling::applyAllReplacements(Replace, Rewrite);
}
int RefactoringTool::saveRewrittenFiles(Rewriter &Rewrite) {
return Rewrite.overwriteChangedFiles() ? 1 : 0;
}
} // end namespace tooling
} // end namespace clang