[clang-tools-extra] Adopt FileManager's error-returning APIs
[lldb.git] / clang-tools-extra / clang-apply-replacements / lib / Tooling / ApplyReplacements.cpp
1 //===-- ApplyReplacements.cpp - Apply and deduplicate replacements --------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 ///
9 /// \file
10 /// \brief This file provides the implementation for deduplicating, detecting
11 /// conflicts in, and applying collections of Replacements.
12 ///
13 /// FIXME: Use Diagnostics for output instead of llvm::errs().
14 ///
15 //===----------------------------------------------------------------------===//
16 #include "clang-apply-replacements/Tooling/ApplyReplacements.h"
17 #include "clang/Basic/LangOptions.h"
18 #include "clang/Basic/SourceManager.h"
19 #include "clang/Format/Format.h"
20 #include "clang/Lex/Lexer.h"
21 #include "clang/Rewrite/Core/Rewriter.h"
22 #include "clang/Tooling/Core/Diagnostic.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 collectReplacementsFromDirectory(
84     const llvm::StringRef Directory, TUDiagnostics &TUs,
85     TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {
86   using namespace llvm::sys::fs;
87   using namespace llvm::sys::path;
88
89   std::error_code ErrorCode;
90
91   for (recursive_directory_iterator I(Directory, ErrorCode), E;
92        I != E && !ErrorCode; I.increment(ErrorCode)) {
93     if (filename(I->path())[0] == '.') {
94       // Indicate not to descend into directories beginning with '.'
95       I.no_push();
96       continue;
97     }
98
99     if (extension(I->path()) != ".yaml")
100       continue;
101
102     TUFiles.push_back(I->path());
103
104     ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
105         MemoryBuffer::getFile(I->path());
106     if (std::error_code BufferError = Out.getError()) {
107       errs() << "Error reading " << I->path() << ": " << BufferError.message()
108              << "\n";
109       continue;
110     }
111
112     yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
113     tooling::TranslationUnitDiagnostics TU;
114     YIn >> TU;
115     if (YIn.error()) {
116       // File doesn't appear to be a header change description. Ignore it.
117       continue;
118     }
119
120     // Only keep files that properly parse.
121     TUs.push_back(TU);
122   }
123
124   return ErrorCode;
125 }
126
127 /// \brief Extract replacements from collected TranslationUnitReplacements and
128 /// TranslationUnitDiagnostics and group them per file. Identical replacements
129 /// from diagnostics are deduplicated.
130 ///
131 /// \param[in] TUs Collection of all found and deserialized
132 /// TranslationUnitReplacements.
133 /// \param[in] TUDs Collection of all found and deserialized
134 /// TranslationUnitDiagnostics.
135 /// \param[in] SM Used to deduplicate paths.
136 ///
137 /// \returns A map mapping FileEntry to a set of Replacement targeting that
138 /// file.
139 static llvm::DenseMap<const FileEntry *, std::vector<tooling::Replacement>>
140 groupReplacements(const TUReplacements &TUs, const TUDiagnostics &TUDs,
141                   const clang::SourceManager &SM) {
142   std::set<StringRef> Warned;
143   llvm::DenseMap<const FileEntry *, std::vector<tooling::Replacement>>
144       GroupedReplacements;
145
146   // Deduplicate identical replacements in diagnostics.
147   // FIXME: Find an efficient way to deduplicate on diagnostics level.
148   llvm::DenseMap<const FileEntry *, std::set<tooling::Replacement>>
149       DiagReplacements;
150
151   auto AddToGroup = [&](const tooling::Replacement &R, bool FromDiag) {
152     // Use the file manager to deduplicate paths. FileEntries are
153     // automatically canonicalized.
154     if (auto Entry = SM.getFileManager().getFile(R.getFilePath())) {
155       if (FromDiag) {
156         auto &Replaces = DiagReplacements[*Entry];
157         if (!Replaces.insert(R).second)
158           return;
159       }
160       GroupedReplacements[*Entry].push_back(R);
161     } else if (Warned.insert(R.getFilePath()).second) {
162       errs() << "Described file '" << R.getFilePath()
163              << "' doesn't exist. Ignoring...\n";
164     }
165   };
166
167   for (const auto &TU : TUs)
168     for (const tooling::Replacement &R : TU.Replacements)
169       AddToGroup(R, false);
170
171   for (const auto &TU : TUDs)
172     for (const auto &D : TU.Diagnostics)
173       if (const auto *ChoosenFix = tooling::selectFirstFix(D)) {
174         for (const auto &Fix : *ChoosenFix)
175           for (const tooling::Replacement &R : Fix.second)
176             AddToGroup(R, true);
177       }
178
179   // Sort replacements per file to keep consistent behavior when
180   // clang-apply-replacements run on differents machine.
181   for (auto &FileAndReplacements : GroupedReplacements) {
182     llvm::sort(FileAndReplacements.second.begin(),
183                FileAndReplacements.second.end());
184   }
185
186   return GroupedReplacements;
187 }
188
189 bool mergeAndDeduplicate(const TUReplacements &TUs, const TUDiagnostics &TUDs,
190                          FileToChangesMap &FileChanges,
191                          clang::SourceManager &SM) {
192   auto GroupedReplacements = groupReplacements(TUs, TUDs, SM);
193   bool ConflictDetected = false;
194
195   // To report conflicting replacements on corresponding file, all replacements
196   // are stored into 1 big AtomicChange.
197   for (const auto &FileAndReplacements : GroupedReplacements) {
198     const FileEntry *Entry = FileAndReplacements.first;
199     const SourceLocation BeginLoc =
200         SM.getLocForStartOfFile(SM.getOrCreateFileID(Entry, SrcMgr::C_User));
201     tooling::AtomicChange FileChange(Entry->getName(), Entry->getName());
202     for (const auto &R : FileAndReplacements.second) {
203       llvm::Error Err =
204           FileChange.replace(SM, BeginLoc.getLocWithOffset(R.getOffset()),
205                              R.getLength(), R.getReplacementText());
206       if (Err) {
207         // FIXME: This will report conflicts by pair using a file+offset format
208         // which is not so much human readable.
209         // A first improvement could be to translate offset to line+col. For
210         // this and without loosing error message some modifications arround
211         // `tooling::ReplacementError` are need (access to
212         // `getReplacementErrString`).
213         // A better strategy could be to add a pretty printer methods for
214         // conflict reporting. Methods that could be parameterized to report a
215         // conflict in different format, file+offset, file+line+col, or even
216         // more human readable using VCS conflict markers.
217         // For now, printing directly the error reported by `AtomicChange` is
218         // the easiest solution.
219         errs() << llvm::toString(std::move(Err)) << "\n";
220         ConflictDetected = true;
221       }
222     }
223     FileChanges.try_emplace(Entry,
224                             std::vector<tooling::AtomicChange>{FileChange});
225   }
226
227   return !ConflictDetected;
228 }
229
230 llvm::Expected<std::string>
231 applyChanges(StringRef File, const std::vector<tooling::AtomicChange> &Changes,
232              const tooling::ApplyChangesSpec &Spec,
233              DiagnosticsEngine &Diagnostics) {
234   FileManager Files((FileSystemOptions()));
235   SourceManager SM(Diagnostics, Files);
236
237   llvm::ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
238       SM.getFileManager().getBufferForFile(File);
239   if (!Buffer)
240     return errorCodeToError(Buffer.getError());
241   return tooling::applyAtomicChanges(File, Buffer.get()->getBuffer(), Changes,
242                                      Spec);
243 }
244
245 bool deleteReplacementFiles(const TUReplacementFiles &Files,
246                             clang::DiagnosticsEngine &Diagnostics) {
247   bool Success = true;
248   for (const auto &Filename : Files) {
249     std::error_code Error = llvm::sys::fs::remove(Filename);
250     if (Error) {
251       Success = false;
252       // FIXME: Use Diagnostics for outputting errors.
253       errs() << "Error deleting file: " << Filename << "\n";
254       errs() << Error.message() << "\n";
255       errs() << "Please delete the file manually\n";
256     }
257   }
258   return Success;
259 }
260
261 } // end namespace replace
262 } // end namespace clang