Bump version to 19.1.0git
[llvm-project.git] / clang-tools-extra / clang-apply-replacements / lib / Tooling / ApplyReplacements.cpp
blob9e0da82dfd3806d00de52092e36e11df75782854
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 /// 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/ADT/STLExtras.h"
27 #include "llvm/ADT/StringRef.h"
28 #include "llvm/ADT/StringSet.h"
29 #include "llvm/Support/FileSystem.h"
30 #include "llvm/Support/MemoryBuffer.h"
31 #include "llvm/Support/Path.h"
32 #include "llvm/Support/raw_ostream.h"
33 #include <array>
34 #include <optional>
36 using namespace llvm;
37 using namespace clang;
39 static void eatDiagnostics(const SMDiagnostic &, void *) {}
41 namespace clang {
42 namespace replace {
44 namespace detail {
46 static constexpr std::array<StringRef, 2> AllowedExtensions = {".yaml", ".yml"};
48 template <typename TranslationUnits>
49 static std::error_code collectReplacementsFromDirectory(
50 const llvm::StringRef Directory, TranslationUnits &TUs,
51 TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {
52 using namespace llvm::sys::fs;
53 using namespace llvm::sys::path;
55 std::error_code ErrorCode;
57 for (recursive_directory_iterator I(Directory, ErrorCode), E;
58 I != E && !ErrorCode; I.increment(ErrorCode)) {
59 if (filename(I->path())[0] == '.') {
60 // Indicate not to descend into directories beginning with '.'
61 I.no_push();
62 continue;
65 if (!is_contained(AllowedExtensions, extension(I->path())))
66 continue;
68 TUFiles.push_back(I->path());
70 ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
71 MemoryBuffer::getFile(I->path());
72 if (std::error_code BufferError = Out.getError()) {
73 errs() << "Error reading " << I->path() << ": " << BufferError.message()
74 << "\n";
75 continue;
78 yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
79 typename TranslationUnits::value_type TU;
80 YIn >> TU;
81 if (YIn.error()) {
82 // File doesn't appear to be a header change description. Ignore it.
83 continue;
86 // Only keep files that properly parse.
87 TUs.push_back(TU);
90 return ErrorCode;
92 } // namespace detail
94 template <>
95 std::error_code collectReplacementsFromDirectory(
96 const llvm::StringRef Directory, TUReplacements &TUs,
97 TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {
98 return detail::collectReplacementsFromDirectory(Directory, TUs, TUFiles,
99 Diagnostics);
102 template <>
103 std::error_code collectReplacementsFromDirectory(
104 const llvm::StringRef Directory, TUDiagnostics &TUs,
105 TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {
106 return detail::collectReplacementsFromDirectory(Directory, TUs, TUFiles,
107 Diagnostics);
110 /// Extract replacements from collected TranslationUnitReplacements and
111 /// TranslationUnitDiagnostics and group them per file. Identical replacements
112 /// from diagnostics are deduplicated.
114 /// \param[in] TUs Collection of all found and deserialized
115 /// TranslationUnitReplacements.
116 /// \param[in] TUDs Collection of all found and deserialized
117 /// TranslationUnitDiagnostics.
118 /// \param[in] SM Used to deduplicate paths.
120 /// \returns A map mapping FileEntry to a set of Replacement targeting that
121 /// file.
122 static llvm::DenseMap<FileEntryRef, std::vector<tooling::Replacement>>
123 groupReplacements(const TUReplacements &TUs, const TUDiagnostics &TUDs,
124 const clang::SourceManager &SM) {
125 llvm::StringSet<> Warned;
126 llvm::DenseMap<FileEntryRef, std::vector<tooling::Replacement>>
127 GroupedReplacements;
129 // Deduplicate identical replacements in diagnostics unless they are from the
130 // same TU.
131 // FIXME: Find an efficient way to deduplicate on diagnostics level.
132 llvm::DenseMap<const FileEntry *,
133 std::map<tooling::Replacement,
134 const tooling::TranslationUnitDiagnostics *>>
135 DiagReplacements;
137 auto AddToGroup = [&](const tooling::Replacement &R,
138 const tooling::TranslationUnitDiagnostics *SourceTU,
139 const std::optional<std::string> BuildDir) {
140 // Use the file manager to deduplicate paths. FileEntries are
141 // automatically canonicalized. Since relative paths can come from different
142 // build directories, make them absolute immediately.
143 SmallString<128> Path = R.getFilePath();
144 if (BuildDir)
145 llvm::sys::fs::make_absolute(*BuildDir, Path);
146 else
147 SM.getFileManager().makeAbsolutePath(Path);
149 if (auto Entry = SM.getFileManager().getOptionalFileRef(Path)) {
150 if (SourceTU) {
151 auto &Replaces = DiagReplacements[*Entry];
152 auto It = Replaces.find(R);
153 if (It == Replaces.end())
154 Replaces.emplace(R, SourceTU);
155 else if (It->second != SourceTU)
156 // This replacement is a duplicate of one suggested by another TU.
157 return;
159 GroupedReplacements[*Entry].push_back(R);
160 } else if (Warned.insert(Path).second) {
161 errs() << "Described file '" << R.getFilePath()
162 << "' doesn't exist. Ignoring...\n";
166 for (const auto &TU : TUs)
167 for (const tooling::Replacement &R : TU.Replacements)
168 AddToGroup(R, nullptr, {});
170 for (const auto &TU : TUDs)
171 for (const auto &D : TU.Diagnostics)
172 if (const auto *ChoosenFix = tooling::selectFirstFix(D)) {
173 for (const auto &Fix : *ChoosenFix)
174 for (const tooling::Replacement &R : Fix.second)
175 AddToGroup(R, &TU, D.BuildDirectory);
178 // Sort replacements per file to keep consistent behavior when
179 // clang-apply-replacements run on differents machine.
180 for (auto &FileAndReplacements : GroupedReplacements) {
181 llvm::sort(FileAndReplacements.second);
184 return GroupedReplacements;
187 bool mergeAndDeduplicate(const TUReplacements &TUs, const TUDiagnostics &TUDs,
188 FileToChangesMap &FileChanges,
189 clang::SourceManager &SM, bool IgnoreInsertConflict) {
190 auto GroupedReplacements = groupReplacements(TUs, TUDs, SM);
191 bool ConflictDetected = false;
193 // To report conflicting replacements on corresponding file, all replacements
194 // are stored into 1 big AtomicChange.
195 for (const auto &FileAndReplacements : GroupedReplacements) {
196 FileEntryRef Entry = FileAndReplacements.first;
197 const SourceLocation BeginLoc =
198 SM.getLocForStartOfFile(SM.getOrCreateFileID(Entry, SrcMgr::C_User));
199 tooling::AtomicChange FileChange(Entry.getName(), Entry.getName());
200 for (const auto &R : FileAndReplacements.second) {
201 llvm::Error Err =
202 FileChange.replace(SM, BeginLoc.getLocWithOffset(R.getOffset()),
203 R.getLength(), R.getReplacementText());
204 if (Err) {
205 // FIXME: This will report conflicts by pair using a file+offset format
206 // which is not so much human readable.
207 // A first improvement could be to translate offset to line+col. For
208 // this and without loosing error message some modifications around
209 // `tooling::ReplacementError` are need (access to
210 // `getReplacementErrString`).
211 // A better strategy could be to add a pretty printer methods for
212 // conflict reporting. Methods that could be parameterized to report a
213 // conflict in different format, file+offset, file+line+col, or even
214 // more human readable using VCS conflict markers.
215 // For now, printing directly the error reported by `AtomicChange` is
216 // the easiest solution.
217 errs() << llvm::toString(std::move(Err)) << "\n";
218 if (IgnoreInsertConflict) {
219 tooling::Replacements &Replacements = FileChange.getReplacements();
220 unsigned NewOffset =
221 Replacements.getShiftedCodePosition(R.getOffset());
222 unsigned NewLength = Replacements.getShiftedCodePosition(
223 R.getOffset() + R.getLength()) -
224 NewOffset;
225 if (NewLength == R.getLength()) {
226 tooling::Replacement RR = tooling::Replacement(
227 R.getFilePath(), NewOffset, NewLength, R.getReplacementText());
228 Replacements = Replacements.merge(tooling::Replacements(RR));
229 } else {
230 llvm::errs()
231 << "Can't resolve conflict, skipping the replacement.\n";
232 ConflictDetected = true;
234 } else
235 ConflictDetected = true;
238 FileChanges.try_emplace(Entry,
239 std::vector<tooling::AtomicChange>{FileChange});
242 return !ConflictDetected;
245 llvm::Expected<std::string>
246 applyChanges(StringRef File, const std::vector<tooling::AtomicChange> &Changes,
247 const tooling::ApplyChangesSpec &Spec,
248 DiagnosticsEngine &Diagnostics) {
249 FileManager Files((FileSystemOptions()));
250 SourceManager SM(Diagnostics, Files);
252 llvm::ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
253 SM.getFileManager().getBufferForFile(File);
254 if (!Buffer)
255 return errorCodeToError(Buffer.getError());
256 return tooling::applyAtomicChanges(File, Buffer.get()->getBuffer(), Changes,
257 Spec);
260 bool deleteReplacementFiles(const TUReplacementFiles &Files,
261 clang::DiagnosticsEngine &Diagnostics) {
262 bool Success = true;
263 for (const auto &Filename : Files) {
264 std::error_code Error = llvm::sys::fs::remove(Filename);
265 if (Error) {
266 Success = false;
267 // FIXME: Use Diagnostics for outputting errors.
268 errs() << "Error deleting file: " << Filename << "\n";
269 errs() << Error.message() << "\n";
270 errs() << "Please delete the file manually\n";
273 return Success;
276 } // end namespace replace
277 } // end namespace clang