clang-tools 6.0.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 //===----------------------------------------------------------------------===//
17 #include "clang-apply-replacements/Tooling/ApplyReplacements.h"
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/DiagnosticsYaml.h"
24 #include "clang/Tooling/ReplacementsYaml.h"
25 #include "llvm/ADT/ArrayRef.h"
26 #include "llvm/Support/FileSystem.h"
27 #include "llvm/Support/MemoryBuffer.h"
28 #include "llvm/Support/Path.h"
29 #include "llvm/Support/raw_ostream.h"
30
31 using namespace llvm;
32 using namespace clang;
33
34 static void eatDiagnostics(const SMDiagnostic &, void *) {}
35
36 namespace clang {
37 namespace replace {
38
39 std::error_code collectReplacementsFromDirectory(
40 const llvm::StringRef Directory, TUReplacements &TUs,
41 TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {
42 using namespace llvm::sys::fs;
43 using namespace llvm::sys::path;
44
45 std::error_code ErrorCode;
46
47 for (recursive_directory_iterator I(Directory, ErrorCode), E;
48 I != E && !ErrorCode; I.increment(ErrorCode)) {
49 if (filename(I->path())[0] == '.') {
50 // Indicate not to descend into directories beginning with '.'
51 I.no_push();
52 continue;
53 }
54
55 if (extension(I->path()) != ".yaml")
56 continue;
57
58 TUFiles.push_back(I->path());
59
60 ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
61 MemoryBuffer::getFile(I->path());
62 if (std::error_code BufferError = Out.getError()) {
63 errs() << "Error reading " << I->path() << ": " << BufferError.message()
64 << "\n";
65 continue;
66 }
67
68 yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
69 tooling::TranslationUnitReplacements TU;
70 YIn >> TU;
71 if (YIn.error()) {
72 // File doesn't appear to be a header change description. Ignore it.
73 continue;
74 }
75
76 // Only keep files that properly parse.
77 TUs.push_back(TU);
78 }
79
80 return ErrorCode;
81 }
82
83 std::error_code
84 collectReplacementsFromDirectory(const llvm::StringRef Directory,
85 TUDiagnostics &TUs, TUReplacementFiles &TUFiles,
86 clang::DiagnosticsEngine &Diagnostics) {
87 using namespace llvm::sys::fs;
88 using namespace llvm::sys::path;
89
90 std::error_code ErrorCode;
91
92 for (recursive_directory_iterator I(Directory, ErrorCode), E;
93 I != E && !ErrorCode; I.increment(ErrorCode)) {
94 if (filename(I->path())[0] == '.') {
95 // Indicate not to descend into directories beginning with '.'
96 I.no_push();
97 continue;
98 }
99
100 if (extension(I->path()) != ".yaml")
101 continue;
102
103 TUFiles.push_back(I->path());
104
105 ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
106 MemoryBuffer::getFile(I->path());
107 if (std::error_code BufferError = Out.getError()) {
108 errs() << "Error reading " << I->path() << ": " << BufferError.message()
109 << "\n";
110 continue;
111 }
112
113 yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
114 tooling::TranslationUnitDiagnostics TU;
115 YIn >> TU;
116 if (YIn.error()) {
117 // File doesn't appear to be a header change description. Ignore it.
118 continue;
119 }
120
121 // Only keep files that properly parse.
122 TUs.push_back(TU);
123 }
124
125 return ErrorCode;
126 }
127
128 /// \brief Dumps information for a sequence of conflicting Replacements.
129 ///
130 /// \param[in] File FileEntry for the file the conflicting Replacements are
131 /// for.
132 /// \param[in] ConflictingReplacements List of conflicting Replacements.
133 /// \param[in] SM SourceManager used for reporting.
134 static void reportConflict(
135 const FileEntry *File,
136 const llvm::ArrayRef<clang::tooling::Replacement> ConflictingReplacements,
137 SourceManager &SM) {
138 FileID FID = SM.translateFile(File);
139 if (FID.isInvalid())
140 FID = SM.createFileID(File, SourceLocation(), SrcMgr::C_User);
141
142 // FIXME: Output something a little more user-friendly (e.g. unified diff?)
143 errs() << "The following changes conflict:\n";
144 for (const tooling::Replacement &R : ConflictingReplacements) {
145 if (R.getLength() == 0) {
146 errs() << " Insert at " << SM.getLineNumber(FID, R.getOffset()) << ":"
147 << SM.getColumnNumber(FID, R.getOffset()) << " "
148 << R.getReplacementText() << "\n";
149 } else {
150 if (R.getReplacementText().empty())
151 errs() << " Remove ";
152 else
153 errs() << " Replace ";
154
155 errs() << SM.getLineNumber(FID, R.getOffset()) << ":"
156 << SM.getColumnNumber(FID, R.getOffset()) << "-"
157 << SM.getLineNumber(FID, R.getOffset() + R.getLength() - 1) << ":"
158 << SM.getColumnNumber(FID, R.getOffset() + R.getLength() - 1);
159
160 if (R.getReplacementText().empty())
161 errs() << "\n";
162 else
163 errs() << " with \"" << R.getReplacementText() << "\"\n";
164 }
165 }
166 }
167
168 // FIXME: Remove this function after changing clang-apply-replacements to use
169 // Replacements class.
170 bool applyAllReplacements(const std::vector<tooling::Replacement> &Replaces,
171 Rewriter &Rewrite) {
172 bool Result = true;
173 for (auto I = Replaces.begin(), E = Replaces.end(); I != E; ++I) {
174 if (I->isApplicable()) {
175 Result = I->apply(Rewrite) && Result;
176 } else {
177 Result = false;
178 }
179 }
180 return Result;
181 }
182
183 // FIXME: moved from libToolingCore. remove this when std::vector<Replacement>
184 // is replaced with tooling::Replacements class.
185 static void deduplicate(std::vector<tooling::Replacement> &Replaces,
186 std::vector<tooling::Range> &Conflicts) {
187 if (Replaces.empty())
188 return;
189
190 auto LessNoPath = [](const tooling::Replacement &LHS,
191 const tooling::Replacement &RHS) {
192 if (LHS.getOffset() != RHS.getOffset())
193 return LHS.getOffset() < RHS.getOffset();
194 if (LHS.getLength() != RHS.getLength())
195 return LHS.getLength() < RHS.getLength();
196 return LHS.getReplacementText() < RHS.getReplacementText();
197 };
198
199 auto EqualNoPath = [](const tooling::Replacement &LHS,
200 const tooling::Replacement &RHS) {
201 return LHS.getOffset() == RHS.getOffset() &&
202 LHS.getLength() == RHS.getLength() &&
203 LHS.getReplacementText() == RHS.getReplacementText();
204 };
205
206 // Deduplicate. We don't want to deduplicate based on the path as we assume
207 // that all replacements refer to the same file (or are symlinks).
208 std::sort(Replaces.begin(), Replaces.end(), LessNoPath);
209 Replaces.erase(std::unique(Replaces.begin(), Replaces.end(), EqualNoPath),
210 Replaces.end());
211
212 // Detect conflicts
213 tooling::Range ConflictRange(Replaces.front().getOffset(),
214 Replaces.front().getLength());
215 unsigned ConflictStart = 0;
216 unsigned ConflictLength = 1;
217 for (unsigned i = 1; i < Replaces.size(); ++i) {
218 tooling::Range Current(Replaces[i].getOffset(), Replaces[i].getLength());
219 if (ConflictRange.overlapsWith(Current)) {
220 // Extend conflicted range
221 ConflictRange =
222 tooling::Range(ConflictRange.getOffset(),
223 std::max(ConflictRange.getLength(),
224 Current.getOffset() + Current.getLength() -
225 ConflictRange.getOffset()));
226 ++ConflictLength;
227 } else {
228 if (ConflictLength > 1)
229 Conflicts.push_back(tooling::Range(ConflictStart, ConflictLength));
230 ConflictRange = Current;
231 ConflictStart = i;
232 ConflictLength = 1;
233 }
234 }
235
236 if (ConflictLength > 1)
237 Conflicts.push_back(tooling::Range(ConflictStart, ConflictLength));
238 }
239
240 /// \brief Deduplicates and tests for conflicts among the replacements for each
241 /// file in \c Replacements. Any conflicts found are reported.
242 ///
243 /// \post Replacements[i].getOffset() <= Replacements[i+1].getOffset().
244 ///
245 /// \param[in,out] Replacements Container of all replacements grouped by file
246 /// to be deduplicated and checked for conflicts.
247 /// \param[in] SM SourceManager required for conflict reporting.
248 ///
249 /// \returns \parblock
250 /// \li true if conflicts were detected
251 /// \li false if no conflicts were detected
252 static bool deduplicateAndDetectConflicts(FileToReplacementsMap &Replacements,
253 SourceManager &SM) {
254 bool conflictsFound = false;
255
256 for (auto &FileAndReplacements : Replacements) {
257 const FileEntry *Entry = FileAndReplacements.first;
258 auto &Replacements = FileAndReplacements.second;
259 assert(Entry != nullptr && "No file entry!");
260
261 std::vector<tooling::Range> Conflicts;
262 deduplicate(FileAndReplacements.second, Conflicts);
263
264 if (Conflicts.empty())
265 continue;
266
267 conflictsFound = true;
268
269 errs() << "There are conflicting changes to " << Entry->getName() << ":\n";
270
271 for (const tooling::Range &Conflict : Conflicts) {
272 auto ConflictingReplacements = llvm::makeArrayRef(
273 &Replacements[Conflict.getOffset()], Conflict.getLength());
274 reportConflict(Entry, ConflictingReplacements, SM);
275 }
276 }
277
278 return conflictsFound;
279 }
280
281 bool mergeAndDeduplicate(const TUReplacements &TUs,
282 FileToReplacementsMap &GroupedReplacements,
283 clang::SourceManager &SM) {
284
285 // Group all replacements by target file.
286 std::set<StringRef> Warned;
287 for (const auto &TU : TUs) {
288 for (const tooling::Replacement &R : TU.Replacements) {
289 // Use the file manager to deduplicate paths. FileEntries are
290 // automatically canonicalized.
291 if (const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath())) {
292 GroupedReplacements[Entry].push_back(R);
293 } else if (Warned.insert(R.getFilePath()).second) {
294 errs() << "Described file '" << R.getFilePath()
295 << "' doesn't exist. Ignoring...\n";
296 }
297 }
298 }
299
300 // Ask clang to deduplicate and report conflicts.
301 return !deduplicateAndDetectConflicts(GroupedReplacements, SM);
302 }
303
304 bool mergeAndDeduplicate(const TUDiagnostics &TUs,
305 FileToReplacementsMap &GroupedReplacements,
306 clang::SourceManager &SM) {
307
308 // Group all replacements by target file.
309 std::set<StringRef> Warned;
310 for (const auto &TU : TUs) {
311 for (const auto &D : TU.Diagnostics) {
312 for (const auto &Fix : D.Fix) {
313 for (const tooling::Replacement &R : Fix.second) {
314 // Use the file manager to deduplicate paths. FileEntries are
315 // automatically canonicalized.
316 if (const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath())) {
317 GroupedReplacements[Entry].push_back(R);
318 } else if (Warned.insert(R.getFilePath()).second) {
319 errs() << "Described file '" << R.getFilePath()
320 << "' doesn't exist. Ignoring...\n";
321 }
322 }
323 }
324 }
325 }
326
327 // Ask clang to deduplicate and report conflicts.
328 return !deduplicateAndDetectConflicts(GroupedReplacements, SM);
329 }
330
331 bool applyReplacements(const FileToReplacementsMap &GroupedReplacements,
332 clang::Rewriter &Rewrites) {
333
334 // Apply all changes
335 //
336 // FIXME: No longer certain GroupedReplacements is really the best kind of
337 // data structure for applying replacements. Rewriter certainly doesn't care.
338 // However, until we nail down the design of ReplacementGroups, might as well
339 // leave this as is.
340 for (const auto &FileAndReplacements : GroupedReplacements) {
341 if (!applyAllReplacements(FileAndReplacements.second, Rewrites))
342 return false;
343 }
344
345 return true;
346 }
347
348 RangeVector calculateChangedRanges(
349 const std::vector<clang::tooling::Replacement> &Replaces) {
350 RangeVector ChangedRanges;
351
352 // Generate the new ranges from the replacements.
353 int Shift = 0;
354 for (const tooling::Replacement &R : Replaces) {
355 unsigned Offset = R.getOffset() + Shift;
356 unsigned Length = R.getReplacementText().size();
357 Shift += Length - R.getLength();
358 ChangedRanges.push_back(tooling::Range(Offset, Length));
359 }
360
361 return ChangedRanges;
362 }
363
364 bool writeFiles(const clang::Rewriter &Rewrites) {
365
366 for (auto BufferI = Rewrites.buffer_begin(), BufferE = Rewrites.buffer_end();
367 BufferI != BufferE; ++BufferI) {
368 StringRef FileName =
369 Rewrites.getSourceMgr().getFileEntryForID(BufferI->first)->getName();
370
371 std::error_code EC;
372 llvm::raw_fd_ostream FileStream(FileName, EC, llvm::sys::fs::F_Text);
373 if (EC) {
374 errs() << "Warning: Could not write to " << EC.message() << "\n";
375 continue;
376 }
377 BufferI->second.write(FileStream);
378 }
379
380 return true;
381 }
382
383 bool deleteReplacementFiles(const TUReplacementFiles &Files,
384 clang::DiagnosticsEngine &Diagnostics) {
385 bool Success = true;
386 for (const auto &Filename : Files) {
387 std::error_code Error = llvm::sys::fs::remove(Filename);
388 if (Error) {
389 Success = false;
390 // FIXME: Use Diagnostics for outputting errors.
391 errs() << "Error deleting file: " << Filename << "\n";
392 errs() << Error.message() << "\n";
393 errs() << "Please delete the file manually\n";
394 }
395 }
396 return Success;
397 }
398
399 } // end namespace replace
400 } // end namespace clang
clang::replace::deduplicateAndDetectConflicts
static bool deduplicateAndDetectConflicts(FileToReplacementsMap &Replacements, SourceManager &SM)
Deduplicates and tests for conflicts among the replacements for each file in Replacements.
Definition: ApplyReplacements.cpp:252
llvm
Definition: ClangTidyOptions.cpp:31
clang::replace::reportConflict
static void reportConflict(const FileEntry *File, const llvm::ArrayRef< clang::tooling::Replacement > ConflictingReplacements, SourceManager &SM)
Dumps information for a sequence of conflicting Replacements.
Definition: ApplyReplacements.cpp:134
clang::replace::FileToReplacementsMap
llvm::DenseMap< const clang::FileEntry *, std::vector< clang::tooling::Replacement > > FileToReplacementsMap
Map mapping file name to Replacements targeting that file.
Definition: ApplyReplacements.h:53
clang::replace::deleteReplacementFiles
bool deleteReplacementFiles(const TUReplacementFiles &Files, clang::DiagnosticsEngine &Diagnostics)
Delete the replacement files.
Definition: ApplyReplacements.cpp:383
File
HeaderHandle File
Definition: PreprocessorTracker.cpp:526
clang::replace::TUReplacements
std::vector< clang::tooling::TranslationUnitReplacements > TUReplacements
Collection of TranslationUnitReplacements.
Definition: ApplyReplacements.h:42
eatDiagnostics
static void eatDiagnostics(const SMDiagnostic &, void *)
Definition: ApplyReplacements.cpp:34
clang::replace::writeFiles
bool writeFiles(const clang::Rewriter &Rewrites)
Write the contents of FileContents to disk.
Definition: ApplyReplacements.cpp:364
Directory
static cl::opt< std::string > Directory(cl::Positional, cl::Required, cl::desc("<Search Root Directory>"))
Filename
std::string Filename
Filename as a string.
Definition: IncludeOrderCheck.cpp:38
clang::replace::RangeVector
std::vector< clang::tooling::Range > RangeVector
Collection of source ranges.
Definition: ApplyReplacements.h:39
clang::replace::deduplicate
static void deduplicate(std::vector< tooling::Replacement > &Replaces, std::vector< tooling::Range > &Conflicts)
Definition: ApplyReplacements.cpp:185
clang::replace::calculateChangedRanges
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...
Definition: ApplyReplacements.cpp:348
clang::replace::collectReplacementsFromDirectory
std::error_code collectReplacementsFromDirectory(const llvm::StringRef Directory, TUReplacements &TUs, TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics)
Recursively descends through a directory structure rooted at Directory and attempts to deserialize *...
Definition: ApplyReplacements.cpp:39
clang
Definition: AndroidTidyModule.cpp:28
Range
CharSourceRange Range
SourceRange for the file name.
Definition: IncludeOrderCheck.cpp:37
ApplyReplacements.h
This file provides the interface for deduplicating, detecting conflicts in, and applying collections ...
clang::replace::TUDiagnostics
std::vector< clang::tooling::TranslationUnitDiagnostics > TUDiagnostics
Collection of TranslationUniDiagnostics.
Definition: ApplyReplacements.h:48
clang::replace::TUReplacementFiles
std::vector< std::string > TUReplacementFiles
Collection of TranslationUnitReplacement files.
Definition: ApplyReplacements.h:45
Fix
static cl::opt< bool > Fix("fix", cl::desc(R"( Apply suggested fixes. Without -fix-errors clang-tidy will bail out if any compilation errors were found. )"), cl::init(false), cl::cat(ClangTidyCategory))
clang::replace::applyAllReplacements
bool applyAllReplacements(const std::vector< tooling::Replacement > &Replaces, Rewriter &Rewrite)
Definition: ApplyReplacements.cpp:170
clang::replace::mergeAndDeduplicate
bool mergeAndDeduplicate(const TUReplacements &TUs, FileToReplacementsMap &GroupedReplacements, clang::SourceManager &SM)
Deduplicate, check for conflicts, and apply all Replacements stored in TUs.
Definition: ApplyReplacements.cpp:281
Entry
Definition: Modularize.cpp:430
applyReplacements
static bool applyReplacements(const std::vector< tooling::Replacement > &Replacements, std::string &Result, DiagnosticsEngine &Diagnostics)
Apply Replacements and return the new file contents.
Definition: ClangApplyReplacementsMain.cpp:140