2013-08-30 22:33:56 +08:00
|
|
|
//===-- ApplyReplacements.cpp - Apply and deduplicate replacements --------===//
|
2013-08-22 21:07:14 +08:00
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
///
|
|
|
|
/// \file
|
2013-08-30 22:33:56 +08:00
|
|
|
/// \brief This file provides the implementation for deduplicating, detecting
|
|
|
|
/// conflicts in, and applying collections of Replacements.
|
|
|
|
///
|
|
|
|
/// FIXME: Use Diagnostics for output instead of llvm::errs().
|
2013-08-22 21:07:14 +08:00
|
|
|
///
|
|
|
|
//===----------------------------------------------------------------------===//
|
2013-09-04 01:58:19 +08:00
|
|
|
#include "clang-apply-replacements/Tooling/ApplyReplacements.h"
|
2013-08-22 21:07:14 +08:00
|
|
|
#include "clang/Basic/LangOptions.h"
|
|
|
|
#include "clang/Basic/SourceManager.h"
|
2013-09-30 21:59:21 +08:00
|
|
|
#include "clang/Format/Format.h"
|
|
|
|
#include "clang/Lex/Lexer.h"
|
2013-08-22 21:40:32 +08:00
|
|
|
#include "clang/Rewrite/Core/Rewriter.h"
|
2013-08-29 01:19:10 +08:00
|
|
|
#include "clang/Tooling/ReplacementsYaml.h"
|
2013-08-22 21:07:14 +08:00
|
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
|
|
#include "llvm/Support/FileSystem.h"
|
|
|
|
#include "llvm/Support/MemoryBuffer.h"
|
|
|
|
#include "llvm/Support/Path.h"
|
|
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
|
|
|
|
using namespace llvm;
|
|
|
|
using namespace clang;
|
|
|
|
|
|
|
|
|
|
|
|
static void eatDiagnostics(const SMDiagnostic &, void *) {}
|
|
|
|
|
|
|
|
namespace clang {
|
|
|
|
namespace replace {
|
|
|
|
|
2014-06-12 21:32:11 +08:00
|
|
|
std::error_code
|
2013-08-22 21:07:14 +08:00
|
|
|
collectReplacementsFromDirectory(const llvm::StringRef Directory,
|
|
|
|
TUReplacements &TUs,
|
2013-08-27 03:58:59 +08:00
|
|
|
TUReplacementFiles & TURFiles,
|
2013-08-22 21:07:14 +08:00
|
|
|
clang::DiagnosticsEngine &Diagnostics) {
|
|
|
|
using namespace llvm::sys::fs;
|
|
|
|
using namespace llvm::sys::path;
|
|
|
|
|
2014-06-13 06:08:48 +08:00
|
|
|
std::error_code ErrorCode;
|
2013-08-22 21:07:14 +08:00
|
|
|
|
|
|
|
for (recursive_directory_iterator I(Directory, ErrorCode), E;
|
|
|
|
I != E && !ErrorCode; I.increment(ErrorCode)) {
|
|
|
|
if (filename(I->path())[0] == '.') {
|
|
|
|
// Indicate not to descend into directories beginning with '.'
|
|
|
|
I.no_push();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (extension(I->path()) != ".yaml")
|
|
|
|
continue;
|
|
|
|
|
2013-08-27 03:58:59 +08:00
|
|
|
TURFiles.push_back(I->path());
|
|
|
|
|
2014-07-07 01:43:19 +08:00
|
|
|
ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
|
|
|
|
MemoryBuffer::getFile(I->path());
|
|
|
|
if (std::error_code BufferError = Out.getError()) {
|
2013-08-22 21:07:14 +08:00
|
|
|
errs() << "Error reading " << I->path() << ": " << BufferError.message()
|
|
|
|
<< "\n";
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-07-07 01:43:19 +08:00
|
|
|
yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
|
2013-08-22 21:07:14 +08:00
|
|
|
tooling::TranslationUnitReplacements TU;
|
|
|
|
YIn >> TU;
|
|
|
|
if (YIn.error()) {
|
|
|
|
// File doesn't appear to be a header change description. Ignore it.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only keep files that properly parse.
|
|
|
|
TUs.push_back(TU);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ErrorCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// \brief Dumps information for a sequence of conflicting Replacements.
|
|
|
|
///
|
|
|
|
/// \param[in] File FileEntry for the file the conflicting Replacements are
|
|
|
|
/// for.
|
|
|
|
/// \param[in] ConflictingReplacements List of conflicting Replacements.
|
|
|
|
/// \param[in] SM SourceManager used for reporting.
|
|
|
|
static void reportConflict(
|
|
|
|
const FileEntry *File,
|
|
|
|
const llvm::ArrayRef<clang::tooling::Replacement> ConflictingReplacements,
|
|
|
|
SourceManager &SM) {
|
|
|
|
FileID FID = SM.translateFile(File);
|
|
|
|
if (FID.isInvalid())
|
|
|
|
FID = SM.createFileID(File, SourceLocation(), SrcMgr::C_User);
|
|
|
|
|
|
|
|
// FIXME: Output something a little more user-friendly (e.g. unified diff?)
|
|
|
|
errs() << "The following changes conflict:\n";
|
2014-09-09 22:09:48 +08:00
|
|
|
for (const tooling::Replacement &R : ConflictingReplacements) {
|
|
|
|
if (R.getLength() == 0) {
|
|
|
|
errs() << " Insert at " << SM.getLineNumber(FID, R.getOffset()) << ":"
|
|
|
|
<< SM.getColumnNumber(FID, R.getOffset()) << " "
|
|
|
|
<< R.getReplacementText() << "\n";
|
2013-08-22 21:07:14 +08:00
|
|
|
} else {
|
2014-09-09 22:09:48 +08:00
|
|
|
if (R.getReplacementText().empty())
|
2013-08-22 21:07:14 +08:00
|
|
|
errs() << " Remove ";
|
|
|
|
else
|
|
|
|
errs() << " Replace ";
|
|
|
|
|
2014-09-09 22:09:48 +08:00
|
|
|
errs() << SM.getLineNumber(FID, R.getOffset()) << ":"
|
|
|
|
<< SM.getColumnNumber(FID, R.getOffset()) << "-"
|
|
|
|
<< SM.getLineNumber(FID, R.getOffset() + R.getLength() - 1) << ":"
|
|
|
|
<< SM.getColumnNumber(FID, R.getOffset() + R.getLength() - 1);
|
2013-08-22 21:07:14 +08:00
|
|
|
|
2014-09-09 22:09:48 +08:00
|
|
|
if (R.getReplacementText().empty())
|
2013-08-22 21:07:14 +08:00
|
|
|
errs() << "\n";
|
|
|
|
else
|
2014-09-09 22:09:48 +08:00
|
|
|
errs() << " with \"" << R.getReplacementText() << "\"\n";
|
2013-08-22 21:07:14 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// \brief Deduplicates and tests for conflicts among the replacements for each
|
|
|
|
/// file in \c Replacements. Any conflicts found are reported.
|
|
|
|
///
|
2013-09-30 21:59:21 +08:00
|
|
|
/// \post Replacements[i].getOffset() <= Replacements[i+1].getOffset().
|
|
|
|
///
|
2013-08-22 21:07:14 +08:00
|
|
|
/// \param[in,out] Replacements Container of all replacements grouped by file
|
|
|
|
/// to be deduplicated and checked for conflicts.
|
2013-08-22 21:40:32 +08:00
|
|
|
/// \param[in] SM SourceManager required for conflict reporting.
|
2013-08-22 21:07:14 +08:00
|
|
|
///
|
|
|
|
/// \returns \li true if conflicts were detected
|
|
|
|
/// \li false if no conflicts were detected
|
|
|
|
static bool deduplicateAndDetectConflicts(FileToReplacementsMap &Replacements,
|
|
|
|
SourceManager &SM) {
|
|
|
|
bool conflictsFound = false;
|
|
|
|
|
2014-09-09 21:53:51 +08:00
|
|
|
for (auto &FileAndReplacements : Replacements) {
|
|
|
|
const FileEntry *Entry = FileAndReplacements.first;
|
|
|
|
auto &Replacements = FileAndReplacements.second;
|
|
|
|
assert(Entry != nullptr && "No file entry!");
|
2013-08-22 21:07:14 +08:00
|
|
|
|
|
|
|
std::vector<tooling::Range> Conflicts;
|
2014-09-09 21:53:51 +08:00
|
|
|
tooling::deduplicate(FileAndReplacements.second, Conflicts);
|
2013-08-22 21:07:14 +08:00
|
|
|
|
|
|
|
if (Conflicts.empty())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
conflictsFound = true;
|
|
|
|
|
2014-09-09 21:53:51 +08:00
|
|
|
errs() << "There are conflicting changes to " << Entry->getName() << ":\n";
|
2013-08-22 21:07:14 +08:00
|
|
|
|
2014-09-09 21:53:51 +08:00
|
|
|
for (const tooling::Range &Conflict : Conflicts) {
|
|
|
|
auto ConflictingReplacements = llvm::makeArrayRef(
|
|
|
|
&Replacements[Conflict.getOffset()], Conflict.getLength());
|
2013-08-22 21:07:14 +08:00
|
|
|
reportConflict(Entry, ConflictingReplacements, SM);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return conflictsFound;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool mergeAndDeduplicate(const TUReplacements &TUs,
|
|
|
|
FileToReplacementsMap &GroupedReplacements,
|
2013-08-22 21:40:32 +08:00
|
|
|
clang::SourceManager &SM) {
|
2013-08-22 21:07:14 +08:00
|
|
|
|
|
|
|
// Group all replacements by target file.
|
2014-09-09 21:53:51 +08:00
|
|
|
std::set<StringRef> Warned;
|
|
|
|
for (const auto &TU : TUs) {
|
|
|
|
for (const tooling::Replacement &R : TU.Replacements) {
|
|
|
|
// Use the file manager to deduplicate paths. FileEntries are
|
|
|
|
// automatically canonicalized.
|
|
|
|
const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath());
|
|
|
|
if (!Entry && Warned.insert(R.getFilePath()).second) {
|
|
|
|
errs() << "Described file '" << R.getFilePath()
|
|
|
|
<< "' doesn't exist. Ignoring...\n";
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
GroupedReplacements[Entry].push_back(R);
|
|
|
|
}
|
|
|
|
}
|
2013-08-22 21:07:14 +08:00
|
|
|
|
|
|
|
// Ask clang to deduplicate and report conflicts.
|
|
|
|
if (deduplicateAndDetectConflicts(GroupedReplacements, SM))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-08-22 21:40:32 +08:00
|
|
|
bool applyReplacements(const FileToReplacementsMap &GroupedReplacements,
|
2013-08-29 01:19:10 +08:00
|
|
|
clang::Rewriter &Rewrites) {
|
2013-08-22 21:40:32 +08:00
|
|
|
|
|
|
|
// Apply all changes
|
2013-08-29 01:19:10 +08:00
|
|
|
//
|
|
|
|
// FIXME: No longer certain GroupedReplacements is really the best kind of
|
|
|
|
// data structure for applying replacements. Rewriter certainly doesn't care.
|
|
|
|
// However, until we nail down the design of ReplacementGroups, might as well
|
|
|
|
// leave this as is.
|
2014-09-09 21:53:51 +08:00
|
|
|
for (const auto &FileAndReplacements : GroupedReplacements) {
|
|
|
|
if (!tooling::applyAllReplacements(FileAndReplacements.second, Rewrites))
|
2013-08-22 21:40:32 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-08-29 01:19:10 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-09-30 21:59:21 +08:00
|
|
|
RangeVector calculateChangedRanges(
|
|
|
|
const std::vector<clang::tooling::Replacement> &Replaces) {
|
|
|
|
RangeVector ChangedRanges;
|
|
|
|
|
|
|
|
// Generate the new ranges from the replacements.
|
|
|
|
//
|
|
|
|
// NOTE: This is O(n^2) in the number of replacements. If this starts to
|
|
|
|
// become a problem inline shiftedCodePosition() here and do shifts in a
|
|
|
|
// single run through this loop.
|
2014-09-09 22:09:48 +08:00
|
|
|
for (const tooling::Replacement &R : Replaces) {
|
2013-09-30 21:59:21 +08:00
|
|
|
unsigned Offset = tooling::shiftedCodePosition(Replaces, R.getOffset());
|
|
|
|
unsigned Length = R.getReplacementText().size();
|
|
|
|
|
|
|
|
ChangedRanges.push_back(tooling::Range(Offset, Length));
|
|
|
|
}
|
|
|
|
|
|
|
|
return ChangedRanges;
|
|
|
|
}
|
|
|
|
|
2013-08-29 01:19:10 +08:00
|
|
|
bool writeFiles(const clang::Rewriter &Rewrites) {
|
|
|
|
|
|
|
|
for (Rewriter::const_buffer_iterator BufferI = Rewrites.buffer_begin(),
|
|
|
|
BufferE = Rewrites.buffer_end();
|
2013-08-22 21:40:32 +08:00
|
|
|
BufferI != BufferE; ++BufferI) {
|
2013-08-29 01:19:10 +08:00
|
|
|
const char *FileName =
|
|
|
|
Rewrites.getSourceMgr().getFileEntryForID(BufferI->first)->getName();
|
|
|
|
|
2014-08-26 02:17:00 +08:00
|
|
|
std::error_code EC;
|
|
|
|
llvm::raw_fd_ostream FileStream(FileName, EC, llvm::sys::fs::F_Text);
|
|
|
|
if (EC) {
|
|
|
|
errs() << "Warning: Could not write to " << EC.message() << "\n";
|
2013-08-29 01:19:10 +08:00
|
|
|
continue;
|
2013-08-22 21:40:32 +08:00
|
|
|
}
|
|
|
|
BufferI->second.write(FileStream);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-08-27 03:58:59 +08:00
|
|
|
bool deleteReplacementFiles(const TUReplacementFiles &Files,
|
|
|
|
clang::DiagnosticsEngine &Diagnostics) {
|
|
|
|
bool Success = true;
|
2014-09-09 22:09:48 +08:00
|
|
|
for (const auto &Filename : Files) {
|
|
|
|
std::error_code Error = llvm::sys::fs::remove(Filename);
|
2013-08-27 03:58:59 +08:00
|
|
|
if (Error) {
|
|
|
|
Success = false;
|
|
|
|
// FIXME: Use Diagnostics for outputting errors.
|
2014-09-09 22:09:48 +08:00
|
|
|
errs() << "Error deleting file: " << Filename << "\n";
|
2013-08-27 03:58:59 +08:00
|
|
|
errs() << Error.message() << "\n";
|
|
|
|
errs() << "Please delete the file manually\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
|
2013-08-22 21:07:14 +08:00
|
|
|
} // end namespace replace
|
|
|
|
} // end namespace clang
|