clang-tools 20.0.0git
ApplyReplacements.cpp
Go to the documentation of this file.
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//===----------------------------------------------------------------------===//
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>
35
36using namespace llvm;
37using namespace clang;
38
39static void eatDiagnostics(const SMDiagnostic &, void *) {}
40
41namespace clang {
42namespace replace {
43
44namespace detail {
45
46static constexpr std::array<StringRef, 2> AllowedExtensions = {".yaml", ".yml"};
47
48template <typename TranslationUnits>
49static 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;
54
55 std::error_code ErrorCode;
56
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;
63 }
64
65 if (!is_contained(AllowedExtensions, extension(I->path())))
66 continue;
67
68 TUFiles.push_back(I->path());
69
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;
76 }
77
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;
84 }
85
86 // Only keep files that properly parse.
87 TUs.push_back(TU);
88 }
89
90 return ErrorCode;
91}
92} // namespace detail
93
94template <>
96 const llvm::StringRef Directory, TUReplacements &TUs,
97 TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {
100}
101
102template <>
104 const llvm::StringRef Directory, TUDiagnostics &TUs,
105 TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {
108}
109
110/// Extract replacements from collected TranslationUnitReplacements and
111/// TranslationUnitDiagnostics and group them per file. Identical replacements
112/// from diagnostics are deduplicated.
113///
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.
119///
120/// \returns A map mapping FileEntry to a set of Replacement targeting that
121/// file.
122static llvm::DenseMap<FileEntryRef, std::vector<tooling::Replacement>>
124 const clang::SourceManager &SM) {
125 llvm::StringSet<> Warned;
126 llvm::DenseMap<FileEntryRef, std::vector<tooling::Replacement>>
127 GroupedReplacements;
128
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;
136
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);
148
149 if (auto Entry = SM.getFileManager().getOptionalFileRef(Path)) {
150 if (SourceTU) {
151 auto [It, Inserted] = DiagReplacements[*Entry].try_emplace(R, SourceTU);
152 if (!Inserted && It->second != SourceTU)
153 // This replacement is a duplicate of one suggested by another TU.
154 return;
155 }
156 GroupedReplacements[*Entry].push_back(R);
157 } else if (Warned.insert(Path).second) {
158 errs() << "Described file '" << R.getFilePath()
159 << "' doesn't exist. Ignoring...\n";
160 }
161 };
162
163 for (const auto &TU : TUs)
164 for (const tooling::Replacement &R : TU.Replacements)
165 AddToGroup(R, nullptr, {});
166
167 for (const auto &TU : TUDs)
168 for (const auto &D : TU.Diagnostics)
169 if (const auto *ChoosenFix = tooling::selectFirstFix(D)) {
170 for (const auto &Fix : *ChoosenFix)
171 for (const tooling::Replacement &R : Fix.second)
172 AddToGroup(R, &TU, D.BuildDirectory);
173 }
174
175 // Sort replacements per file to keep consistent behavior when
176 // clang-apply-replacements run on differents machine.
177 for (auto &FileAndReplacements : GroupedReplacements) {
178 llvm::sort(FileAndReplacements.second);
179 }
180
181 return GroupedReplacements;
182}
183
185 FileToChangesMap &FileChanges,
186 clang::SourceManager &SM, bool IgnoreInsertConflict) {
187 auto GroupedReplacements = groupReplacements(TUs, TUDs, SM);
188 bool ConflictDetected = false;
189
190 // To report conflicting replacements on corresponding file, all replacements
191 // are stored into 1 big AtomicChange.
192 for (const auto &FileAndReplacements : GroupedReplacements) {
193 FileEntryRef Entry = FileAndReplacements.first;
194 const SourceLocation BeginLoc =
195 SM.getLocForStartOfFile(SM.getOrCreateFileID(Entry, SrcMgr::C_User));
196 tooling::AtomicChange FileChange(Entry.getName(), Entry.getName());
197 for (const auto &R : FileAndReplacements.second) {
198 llvm::Error Err =
199 FileChange.replace(SM, BeginLoc.getLocWithOffset(R.getOffset()),
200 R.getLength(), R.getReplacementText());
201 if (Err) {
202 // FIXME: This will report conflicts by pair using a file+offset format
203 // which is not so much human readable.
204 // A first improvement could be to translate offset to line+col. For
205 // this and without loosing error message some modifications around
206 // `tooling::ReplacementError` are need (access to
207 // `getReplacementErrString`).
208 // A better strategy could be to add a pretty printer methods for
209 // conflict reporting. Methods that could be parameterized to report a
210 // conflict in different format, file+offset, file+line+col, or even
211 // more human readable using VCS conflict markers.
212 // For now, printing directly the error reported by `AtomicChange` is
213 // the easiest solution.
214 errs() << llvm::toString(std::move(Err)) << "\n";
216 tooling::Replacements &Replacements = FileChange.getReplacements();
217 unsigned NewOffset =
218 Replacements.getShiftedCodePosition(R.getOffset());
219 unsigned NewLength = Replacements.getShiftedCodePosition(
220 R.getOffset() + R.getLength()) -
221 NewOffset;
222 if (NewLength == R.getLength()) {
223 tooling::Replacement RR = tooling::Replacement(
224 R.getFilePath(), NewOffset, NewLength, R.getReplacementText());
225 Replacements = Replacements.merge(tooling::Replacements(RR));
226 } else {
227 llvm::errs()
228 << "Can't resolve conflict, skipping the replacement.\n";
229 ConflictDetected = true;
230 }
231 } else
232 ConflictDetected = true;
233 }
234 }
235 FileChanges.try_emplace(Entry,
236 std::vector<tooling::AtomicChange>{FileChange});
237 }
238
239 return !ConflictDetected;
240}
241
242llvm::Expected<std::string>
243applyChanges(StringRef File, const std::vector<tooling::AtomicChange> &Changes,
244 const tooling::ApplyChangesSpec &Spec,
245 DiagnosticsEngine &Diagnostics) {
246 FileManager Files((FileSystemOptions()));
247 SourceManager SM(Diagnostics, Files);
248
249 llvm::ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
250 SM.getFileManager().getBufferForFile(File);
251 if (!Buffer)
252 return errorCodeToError(Buffer.getError());
253 return tooling::applyAtomicChanges(File, Buffer.get()->getBuffer(), Changes,
254 Spec);
255}
256
258 clang::DiagnosticsEngine &Diagnostics) {
259 bool Success = true;
260 for (const auto &Filename : Files) {
261 std::error_code Error = llvm::sys::fs::remove(Filename);
262 if (Error) {
263 Success = false;
264 // FIXME: Use Diagnostics for outputting errors.
265 errs() << "Error deleting file: " << Filename << "\n";
266 errs() << Error.message() << "\n";
267 errs() << "Please delete the file manually\n";
268 }
269 }
270 return Success;
271}
272
273} // end namespace replace
274} // end namespace clang
static void eatDiagnostics(const SMDiagnostic &, void *)
This file provides the interface for deduplicating, detecting conflicts in, and applying collections ...
const Expr * E
static cl::opt< bool > IgnoreInsertConflict("ignore-insert-conflict", cl::desc("Ignore insert conflict and keep running to fix."), cl::init(false), cl::cat(ReplacementCategory))
static cl::opt< bool > Fix("fix", 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))
StringRef BuildDir
Definition: ClangTidy.cpp:305
static constexpr llvm::SourceMgr::DiagKind Error
CompiledFragmentImpl & Out
tooling::Replacements Changes
Definition: Format.cpp:109
std::string Filename
Filename as a string.
std::vector< HeaderHandle > Path
llvm::StringRef Directory
WantDiagnostics Diagnostics
static constexpr std::array< StringRef, 2 > AllowedExtensions
static std::error_code collectReplacementsFromDirectory(const llvm::StringRef Directory, TranslationUnits &TUs, TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics)
llvm::DenseMap< clang::FileEntryRef, std::vector< tooling::AtomicChange > > FileToChangesMap
Map mapping file name to a set of AtomicChange targeting that file.
bool mergeAndDeduplicate(const TUReplacements &TUs, const TUDiagnostics &TUDs, FileToChangesMap &FileChanges, clang::SourceManager &SM, bool IgnoreInsertConflict=false)
Deduplicate, check for conflicts, and extract all Replacements stored in TUs.
static llvm::DenseMap< FileEntryRef, std::vector< tooling::Replacement > > groupReplacements(const TUReplacements &TUs, const TUDiagnostics &TUDs, const clang::SourceManager &SM)
Extract replacements from collected TranslationUnitReplacements and TranslationUnitDiagnostics and gr...
bool deleteReplacementFiles(const TUReplacementFiles &Files, clang::DiagnosticsEngine &Diagnostics)
Delete the replacement files.
std::error_code collectReplacementsFromDirectory(const llvm::StringRef Directory, TranslationUnits &TUs, TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics)=delete
Recursively descends through a directory structure rooted at Directory and attempts to deserialize *....
std::vector< clang::tooling::TranslationUnitReplacements > TUReplacements
Collection of TranslationUnitReplacements.
std::vector< std::string > TUReplacementFiles
Collection of TranslationUnitReplacement files.
llvm::Expected< std::string > applyChanges(StringRef File, const std::vector< tooling::AtomicChange > &Changes, const tooling::ApplyChangesSpec &Spec, DiagnosticsEngine &Diagnostics)
Apply AtomicChange on File and rewrite it.
std::vector< clang::tooling::TranslationUnitDiagnostics > TUDiagnostics
Collection of TranslationUniDiagnostics.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Some operations such as code completion produce a set of candidates.
Definition: Generators.h:58