clang-tools  3.9.0
ApplyReplacements.cpp
Go to the documentation of this file.
1 //===-- ApplyReplacements.cpp - Apply and deduplicate replacements --------===//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 ///
10 /// \file
11 /// \brief This file provides the implementation for deduplicating, detecting
12 /// conflicts in, and applying collections of Replacements.
13 ///
14 /// FIXME: Use Diagnostics for output instead of llvm::errs().
15 ///
16 //===----------------------------------------------------------------------===//
18 #include "clang/Basic/LangOptions.h"
19 #include "clang/Basic/SourceManager.h"
20 #include "clang/Format/Format.h"
21 #include "clang/Lex/Lexer.h"
22 #include "clang/Rewrite/Core/Rewriter.h"
23 #include "clang/Tooling/ReplacementsYaml.h"
24 #include "llvm/ADT/ArrayRef.h"
25 #include "llvm/Support/FileSystem.h"
26 #include "llvm/Support/MemoryBuffer.h"
27 #include "llvm/Support/Path.h"
28 #include "llvm/Support/raw_ostream.h"
29 
30 using namespace llvm;
31 using namespace clang;
32 
33 
34 static void eatDiagnostics(const SMDiagnostic &, void *) {}
35 
36 namespace clang {
37 namespace replace {
38 
39 std::error_code
41  TUReplacements &TUs,
42  TUReplacementFiles & TURFiles,
43  clang::DiagnosticsEngine &Diagnostics) {
44  using namespace llvm::sys::fs;
45  using namespace llvm::sys::path;
46 
47  std::error_code ErrorCode;
48 
49  for (recursive_directory_iterator I(Directory, ErrorCode), E;
50  I != E && !ErrorCode; I.increment(ErrorCode)) {
51  if (filename(I->path())[0] == '.') {
52  // Indicate not to descend into directories beginning with '.'
53  I.no_push();
54  continue;
55  }
56 
57  if (extension(I->path()) != ".yaml")
58  continue;
59 
60  TURFiles.push_back(I->path());
61 
62  ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
63  MemoryBuffer::getFile(I->path());
64  if (std::error_code BufferError = Out.getError()) {
65  errs() << "Error reading " << I->path() << ": " << BufferError.message()
66  << "\n";
67  continue;
68  }
69 
70  yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
71  tooling::TranslationUnitReplacements TU;
72  YIn >> TU;
73  if (YIn.error()) {
74  // File doesn't appear to be a header change description. Ignore it.
75  continue;
76  }
77 
78  // Only keep files that properly parse.
79  TUs.push_back(TU);
80  }
81 
82  return ErrorCode;
83 }
84 
85 /// \brief Dumps information for a sequence of conflicting Replacements.
86 ///
87 /// \param[in] File FileEntry for the file the conflicting Replacements are
88 /// for.
89 /// \param[in] ConflictingReplacements List of conflicting Replacements.
90 /// \param[in] SM SourceManager used for reporting.
91 static void reportConflict(
92  const FileEntry *File,
93  const llvm::ArrayRef<clang::tooling::Replacement> ConflictingReplacements,
94  SourceManager &SM) {
95  FileID FID = SM.translateFile(File);
96  if (FID.isInvalid())
97  FID = SM.createFileID(File, SourceLocation(), SrcMgr::C_User);
98 
99  // FIXME: Output something a little more user-friendly (e.g. unified diff?)
100  errs() << "The following changes conflict:\n";
101  for (const tooling::Replacement &R : ConflictingReplacements) {
102  if (R.getLength() == 0) {
103  errs() << " Insert at " << SM.getLineNumber(FID, R.getOffset()) << ":"
104  << SM.getColumnNumber(FID, R.getOffset()) << " "
105  << R.getReplacementText() << "\n";
106  } else {
107  if (R.getReplacementText().empty())
108  errs() << " Remove ";
109  else
110  errs() << " Replace ";
111 
112  errs() << SM.getLineNumber(FID, R.getOffset()) << ":"
113  << SM.getColumnNumber(FID, R.getOffset()) << "-"
114  << SM.getLineNumber(FID, R.getOffset() + R.getLength() - 1) << ":"
115  << SM.getColumnNumber(FID, R.getOffset() + R.getLength() - 1);
116 
117  if (R.getReplacementText().empty())
118  errs() << "\n";
119  else
120  errs() << " with \"" << R.getReplacementText() << "\"\n";
121  }
122  }
123 }
124 
125 /// \brief Deduplicates and tests for conflicts among the replacements for each
126 /// file in \c Replacements. Any conflicts found are reported.
127 ///
128 /// \post Replacements[i].getOffset() <= Replacements[i+1].getOffset().
129 ///
130 /// \param[in,out] Replacements Container of all replacements grouped by file
131 /// to be deduplicated and checked for conflicts.
132 /// \param[in] SM SourceManager required for conflict reporting.
133 ///
134 /// \returns \parblock
135 /// \li true if conflicts were detected
136 /// \li false if no conflicts were detected
138  SourceManager &SM) {
139  bool conflictsFound = false;
140 
141  for (auto &FileAndReplacements : Replacements) {
142  const FileEntry *Entry = FileAndReplacements.first;
143  auto &Replacements = FileAndReplacements.second;
144  assert(Entry != nullptr && "No file entry!");
145 
146  std::vector<tooling::Range> Conflicts;
147  tooling::deduplicate(FileAndReplacements.second, Conflicts);
148 
149  if (Conflicts.empty())
150  continue;
151 
152  conflictsFound = true;
153 
154  errs() << "There are conflicting changes to " << Entry->getName() << ":\n";
155 
156  for (const tooling::Range &Conflict : Conflicts) {
157  auto ConflictingReplacements = llvm::makeArrayRef(
158  &Replacements[Conflict.getOffset()], Conflict.getLength());
159  reportConflict(Entry, ConflictingReplacements, SM);
160  }
161  }
162 
163  return conflictsFound;
164 }
165 
167  FileToReplacementsMap &GroupedReplacements,
168  clang::SourceManager &SM) {
169 
170  // Group all replacements by target file.
171  std::set<StringRef> Warned;
172  for (const auto &TU : TUs) {
173  for (const tooling::Replacement &R : TU.Replacements) {
174  // Use the file manager to deduplicate paths. FileEntries are
175  // automatically canonicalized.
176  const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath());
177  if (!Entry && Warned.insert(R.getFilePath()).second) {
178  errs() << "Described file '" << R.getFilePath()
179  << "' doesn't exist. Ignoring...\n";
180  continue;
181  }
182  GroupedReplacements[Entry].push_back(R);
183  }
184  }
185 
186  // Ask clang to deduplicate and report conflicts.
187  return !deduplicateAndDetectConflicts(GroupedReplacements, SM);
188 }
189 
190 bool applyReplacements(const FileToReplacementsMap &GroupedReplacements,
191  clang::Rewriter &Rewrites) {
192 
193  // Apply all changes
194  //
195  // FIXME: No longer certain GroupedReplacements is really the best kind of
196  // data structure for applying replacements. Rewriter certainly doesn't care.
197  // However, until we nail down the design of ReplacementGroups, might as well
198  // leave this as is.
199  for (const auto &FileAndReplacements : GroupedReplacements) {
200  if (!tooling::applyAllReplacements(FileAndReplacements.second, Rewrites))
201  return false;
202  }
203 
204  return true;
205 }
206 
208  const std::vector<clang::tooling::Replacement> &Replaces) {
209  RangeVector ChangedRanges;
210 
211  // Generate the new ranges from the replacements.
212  int Shift = 0;
213  for (const tooling::Replacement &R : Replaces) {
214  unsigned Offset = R.getOffset() + Shift;
215  unsigned Length = R.getReplacementText().size();
216  Shift += Length - R.getLength();
217  ChangedRanges.push_back(tooling::Range(Offset, Length));
218  }
219 
220  return ChangedRanges;
221 }
222 
223 bool writeFiles(const clang::Rewriter &Rewrites) {
224 
225  for (Rewriter::const_buffer_iterator BufferI = Rewrites.buffer_begin(),
226  BufferE = Rewrites.buffer_end();
227  BufferI != BufferE; ++BufferI) {
228  const char *FileName =
229  Rewrites.getSourceMgr().getFileEntryForID(BufferI->first)->getName();
230 
231  std::error_code EC;
232  llvm::raw_fd_ostream FileStream(FileName, EC, llvm::sys::fs::F_Text);
233  if (EC) {
234  errs() << "Warning: Could not write to " << EC.message() << "\n";
235  continue;
236  }
237  BufferI->second.write(FileStream);
238  }
239 
240  return true;
241 }
242 
244  clang::DiagnosticsEngine &Diagnostics) {
245  bool Success = true;
246  for (const auto &Filename : Files) {
247  std::error_code Error = llvm::sys::fs::remove(Filename);
248  if (Error) {
249  Success = false;
250  // FIXME: Use Diagnostics for outputting errors.
251  errs() << "Error deleting file: " << Filename << "\n";
252  errs() << Error.message() << "\n";
253  errs() << "Please delete the file manually\n";
254  }
255  }
256  return Success;
257 }
258 
259 } // end namespace replace
260 } // end namespace clang
static bool deduplicateAndDetectConflicts(FileToReplacementsMap &Replacements, SourceManager &SM)
Deduplicates and tests for conflicts among the replacements for each file in Replacements.
static void reportConflict(const FileEntry *File, const llvm::ArrayRef< clang::tooling::Replacement > ConflictingReplacements, SourceManager &SM)
Dumps information for a sequence of conflicting Replacements.
llvm::DenseMap< const clang::FileEntry *, std::vector< clang::tooling::Replacement > > FileToReplacementsMap
Map mapping file name to Replacements targeting that file.
bool deleteReplacementFiles(const TUReplacementFiles &Files, clang::DiagnosticsEngine &Diagnostics)
Delete the replacement files.
HeaderHandle File
std::vector< clang::tooling::TranslationUnitReplacements > TUReplacements
Collection of TranslationUnitReplacements.
static void eatDiagnostics(const SMDiagnostic &, void *)
std::error_code collectReplacementsFromDirectory(const llvm::StringRef Directory, TUReplacements &TUs, TUReplacementFiles &TURFiles, clang::DiagnosticsEngine &Diagnostics)
Recursively descends through a directory structure rooted at Directory and attempts to deserialize *...
bool writeFiles(const clang::Rewriter &Rewrites)
Write the contents of FileContents to disk.
static cl::opt< std::string > Directory(cl::Positional, cl::Required, cl::desc("<Search Root Directory>"))
SourceManager & SM
std::string Filename
Filename as a string.
std::vector< clang::tooling::Range > RangeVector
Collection of source ranges.
RangeVector calculateChangedRanges(const std::vector< clang::tooling::Replacement > &Replacements)
Given a collection of Replacements for a single file, produces a list of source ranges that enclose t...
CharSourceRange Range
SourceRange for the file name.
FileManager Files
Definition: ClangTidy.cpp:188
This file provides the interface for deduplicating, detecting conflicts in, and applying collections ...
std::vector< std::string > TUReplacementFiles
Collection of TranslationUnitReplacement files.
bool mergeAndDeduplicate(const TUReplacements &TUs, FileToReplacementsMap &GroupedReplacements, clang::SourceManager &SM)
Deduplicate, check for conflicts, and apply all Replacements stored in TUs.
static bool applyReplacements(const std::vector< tooling::Replacement > &Replacements, std::string &Result, DiagnosticsEngine &Diagnostics)
Apply Replacements and return the new file contents.