clang-tools  11.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/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 
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 
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 /// 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>>
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 unless they are from the
147  // same TU.
148  // FIXME: Find an efficient way to deduplicate on diagnostics level.
149  llvm::DenseMap<const FileEntry *,
150  std::map<tooling::Replacement,
151  const tooling::TranslationUnitDiagnostics *>>
152  DiagReplacements;
153 
154  auto AddToGroup = [&](const tooling::Replacement &R,
155  const tooling::TranslationUnitDiagnostics *SourceTU) {
156  // Use the file manager to deduplicate paths. FileEntries are
157  // automatically canonicalized.
158  if (auto Entry = SM.getFileManager().getFile(R.getFilePath())) {
159  if (SourceTU) {
160  auto &Replaces = DiagReplacements[*Entry];
161  auto It = Replaces.find(R);
162  if (It == Replaces.end())
163  Replaces.emplace(R, SourceTU);
164  else if (It->second != SourceTU)
165  // This replacement is a duplicate of one suggested by another TU.
166  return;
167  }
168  GroupedReplacements[*Entry].push_back(R);
169  } else if (Warned.insert(R.getFilePath()).second) {
170  errs() << "Described file '" << R.getFilePath()
171  << "' doesn't exist. Ignoring...\n";
172  }
173  };
174 
175  for (const auto &TU : TUs)
176  for (const tooling::Replacement &R : TU.Replacements)
177  AddToGroup(R, nullptr);
178 
179  for (const auto &TU : TUDs)
180  for (const auto &D : TU.Diagnostics)
181  if (const auto *ChoosenFix = tooling::selectFirstFix(D)) {
182  for (const auto &Fix : *ChoosenFix)
183  for (const tooling::Replacement &R : Fix.second)
184  AddToGroup(R, &TU);
185  }
186 
187  // Sort replacements per file to keep consistent behavior when
188  // clang-apply-replacements run on differents machine.
189  for (auto &FileAndReplacements : GroupedReplacements) {
190  llvm::sort(FileAndReplacements.second.begin(),
191  FileAndReplacements.second.end());
192  }
193 
194  return GroupedReplacements;
195 }
196 
197 bool mergeAndDeduplicate(const TUReplacements &TUs, const TUDiagnostics &TUDs,
198  FileToChangesMap &FileChanges,
199  clang::SourceManager &SM) {
200  auto GroupedReplacements = groupReplacements(TUs, TUDs, SM);
201  bool ConflictDetected = false;
202 
203  // To report conflicting replacements on corresponding file, all replacements
204  // are stored into 1 big AtomicChange.
205  for (const auto &FileAndReplacements : GroupedReplacements) {
206  const FileEntry *Entry = FileAndReplacements.first;
207  const SourceLocation BeginLoc =
208  SM.getLocForStartOfFile(SM.getOrCreateFileID(Entry, SrcMgr::C_User));
209  tooling::AtomicChange FileChange(Entry->getName(), Entry->getName());
210  for (const auto &R : FileAndReplacements.second) {
211  llvm::Error Err =
212  FileChange.replace(SM, BeginLoc.getLocWithOffset(R.getOffset()),
213  R.getLength(), R.getReplacementText());
214  if (Err) {
215  // FIXME: This will report conflicts by pair using a file+offset format
216  // which is not so much human readable.
217  // A first improvement could be to translate offset to line+col. For
218  // this and without loosing error message some modifications around
219  // `tooling::ReplacementError` are need (access to
220  // `getReplacementErrString`).
221  // A better strategy could be to add a pretty printer methods for
222  // conflict reporting. Methods that could be parameterized to report a
223  // conflict in different format, file+offset, file+line+col, or even
224  // more human readable using VCS conflict markers.
225  // For now, printing directly the error reported by `AtomicChange` is
226  // the easiest solution.
227  errs() << llvm::toString(std::move(Err)) << "\n";
228  ConflictDetected = true;
229  }
230  }
231  FileChanges.try_emplace(Entry,
232  std::vector<tooling::AtomicChange>{FileChange});
233  }
234 
235  return !ConflictDetected;
236 }
237 
238 llvm::Expected<std::string>
239 applyChanges(StringRef File, const std::vector<tooling::AtomicChange> &Changes,
240  const tooling::ApplyChangesSpec &Spec,
241  DiagnosticsEngine &Diagnostics) {
242  FileManager Files((FileSystemOptions()));
243  SourceManager SM(Diagnostics, Files);
244 
245  llvm::ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
246  SM.getFileManager().getBufferForFile(File);
247  if (!Buffer)
248  return errorCodeToError(Buffer.getError());
249  return tooling::applyAtomicChanges(File, Buffer.get()->getBuffer(), Changes,
250  Spec);
251 }
252 
254  clang::DiagnosticsEngine &Diagnostics) {
255  bool Success = true;
256  for (const auto &Filename : Files) {
257  std::error_code Error = llvm::sys::fs::remove(Filename);
258  if (Error) {
259  Success = false;
260  // FIXME: Use Diagnostics for outputting errors.
261  errs() << "Error deleting file: " << Filename << "\n";
262  errs() << Error.message() << "\n";
263  errs() << "Please delete the file manually\n";
264  }
265  }
266  return Success;
267 }
268 
269 } // end namespace replace
270 } // end namespace clang
Some operations such as code completion produce a set of candidates.
tooling::Replacements Changes
Definition: Format.cpp:109
bool deleteReplacementFiles(const TUReplacementFiles &Files, clang::DiagnosticsEngine &Diagnostics)
Delete the replacement files.
std::vector< clang::tooling::TranslationUnitReplacements > TUReplacements
Collection of TranslationUnitReplacements.
static llvm::StringRef toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)
static void eatDiagnostics(const SMDiagnostic &, void *)
static std::string replace(llvm::StringRef Haystack, llvm::StringRef Needle, llvm::StringRef Repl)
Definition: TestIndex.cpp:30
static llvm::DenseMap< const FileEntry *, std::vector< tooling::Replacement > > groupReplacements(const TUReplacements &TUs, const TUDiagnostics &TUDs, const clang::SourceManager &SM)
Extract replacements from collected TranslationUnitReplacements and TranslationUnitDiagnostics and gr...
std::string Filename
Filename as a string.
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.
llvm::StringRef Directory
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 *...
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
bool mergeAndDeduplicate(const TUReplacements &TUs, const TUDiagnostics &TUDs, FileToChangesMap &FileChanges, clang::SourceManager &SM)
Deduplicate, check for conflicts, and extract all Replacements stored in TUs.
const Expr * E
This file provides the interface for deduplicating, detecting conflicts in, and applying collections ...
std::vector< clang::tooling::TranslationUnitDiagnostics > TUDiagnostics
Collection of TranslationUniDiagnostics.
std::vector< std::string > TUReplacementFiles
Collection of TranslationUnitReplacement files.
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))
llvm::DenseMap< const clang::FileEntry *, std::vector< tooling::AtomicChange > > FileToChangesMap
Map mapping file name to a set of AtomicChange targeting that file.
llvm::StringMap< std::string > Files