1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
|
//===-- ApplyReplacements.cpp - Apply and deduplicate replacements --------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \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().
///
//===----------------------------------------------------------------------===//
#include "clang-apply-replacements/Tooling/ApplyReplacements.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Format/Format.h"
#include "clang/Lex/Lexer.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/Tooling/ReplacementsYaml.h"
#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 {
std::error_code
collectReplacementsFromDirectory(const llvm::StringRef Directory,
TUReplacements &TUs,
TUReplacementFiles & TURFiles,
clang::DiagnosticsEngine &Diagnostics) {
using namespace llvm::sys::fs;
using namespace llvm::sys::path;
std::error_code ErrorCode;
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;
TURFiles.push_back(I->path());
ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
MemoryBuffer::getFile(I->path());
if (std::error_code BufferError = Out.getError()) {
errs() << "Error reading " << I->path() << ": " << BufferError.message()
<< "\n";
continue;
}
yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
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";
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";
} else {
if (R.getReplacementText().empty())
errs() << " Remove ";
else
errs() << " Replace ";
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);
if (R.getReplacementText().empty())
errs() << "\n";
else
errs() << " with \"" << R.getReplacementText() << "\"\n";
}
}
}
/// \brief Deduplicates and tests for conflicts among the replacements for each
/// file in \c Replacements. Any conflicts found are reported.
///
/// \post Replacements[i].getOffset() <= Replacements[i+1].getOffset().
///
/// \param[in,out] Replacements Container of all replacements grouped by file
/// to be deduplicated and checked for conflicts.
/// \param[in] SM SourceManager required for conflict reporting.
///
/// \returns \parblock
/// \li true if conflicts were detected
/// \li false if no conflicts were detected
static bool deduplicateAndDetectConflicts(FileToReplacementsMap &Replacements,
SourceManager &SM) {
bool conflictsFound = false;
for (auto &FileAndReplacements : Replacements) {
const FileEntry *Entry = FileAndReplacements.first;
auto &Replacements = FileAndReplacements.second;
assert(Entry != nullptr && "No file entry!");
std::vector<tooling::Range> Conflicts;
tooling::deduplicate(FileAndReplacements.second, Conflicts);
if (Conflicts.empty())
continue;
conflictsFound = true;
errs() << "There are conflicting changes to " << Entry->getName() << ":\n";
for (const tooling::Range &Conflict : Conflicts) {
auto ConflictingReplacements = llvm::makeArrayRef(
&Replacements[Conflict.getOffset()], Conflict.getLength());
reportConflict(Entry, ConflictingReplacements, SM);
}
}
return conflictsFound;
}
bool mergeAndDeduplicate(const TUReplacements &TUs,
FileToReplacementsMap &GroupedReplacements,
clang::SourceManager &SM) {
// Group all replacements by target file.
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);
}
}
// Ask clang to deduplicate and report conflicts.
return !deduplicateAndDetectConflicts(GroupedReplacements, SM);
}
bool applyReplacements(const FileToReplacementsMap &GroupedReplacements,
clang::Rewriter &Rewrites) {
// Apply all changes
//
// 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.
for (const auto &FileAndReplacements : GroupedReplacements) {
if (!tooling::applyAllReplacements(FileAndReplacements.second, Rewrites))
return false;
}
return true;
}
RangeVector calculateChangedRanges(
const std::vector<clang::tooling::Replacement> &Replaces) {
RangeVector ChangedRanges;
// Generate the new ranges from the replacements.
int Shift = 0;
for (const tooling::Replacement &R : Replaces) {
unsigned Offset = R.getOffset() + Shift;
unsigned Length = R.getReplacementText().size();
Shift += Length - R.getLength();
ChangedRanges.push_back(tooling::Range(Offset, Length));
}
return ChangedRanges;
}
bool writeFiles(const clang::Rewriter &Rewrites) {
for (Rewriter::const_buffer_iterator BufferI = Rewrites.buffer_begin(),
BufferE = Rewrites.buffer_end();
BufferI != BufferE; ++BufferI) {
const char *FileName =
Rewrites.getSourceMgr().getFileEntryForID(BufferI->first)->getName();
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";
continue;
}
BufferI->second.write(FileStream);
}
return true;
}
bool deleteReplacementFiles(const TUReplacementFiles &Files,
clang::DiagnosticsEngine &Diagnostics) {
bool Success = true;
for (const auto &Filename : Files) {
std::error_code Error = llvm::sys::fs::remove(Filename);
if (Error) {
Success = false;
// FIXME: Use Diagnostics for outputting errors.
errs() << "Error deleting file: " << Filename << "\n";
errs() << Error.message() << "\n";
errs() << "Please delete the file manually\n";
}
}
return Success;
}
} // end namespace replace
} // end namespace clang
|