clang-tools  15.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/Optional.h"
27 #include "llvm/Support/FileSystem.h"
28 #include "llvm/Support/MemoryBuffer.h"
29 #include "llvm/Support/Path.h"
30 #include "llvm/Support/raw_ostream.h"
31 
32 using namespace llvm;
33 using namespace clang;
34 
35 static void eatDiagnostics(const SMDiagnostic &, void *) {}
36 
37 namespace clang {
38 namespace replace {
39 
41  const llvm::StringRef Directory, TUReplacements &TUs,
42  TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {
43  using namespace llvm::sys::fs;
44  using namespace llvm::sys::path;
45 
46  std::error_code ErrorCode;
47 
48  for (recursive_directory_iterator I(Directory, ErrorCode), E;
49  I != E && !ErrorCode; I.increment(ErrorCode)) {
50  if (filename(I->path())[0] == '.') {
51  // Indicate not to descend into directories beginning with '.'
52  I.no_push();
53  continue;
54  }
55 
56  if (extension(I->path()) != ".yaml")
57  continue;
58 
59  TUFiles.push_back(I->path());
60 
61  ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
62  MemoryBuffer::getFile(I->path());
63  if (std::error_code BufferError = Out.getError()) {
64  errs() << "Error reading " << I->path() << ": " << BufferError.message()
65  << "\n";
66  continue;
67  }
68 
69  yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
70  tooling::TranslationUnitReplacements TU;
71  YIn >> TU;
72  if (YIn.error()) {
73  // File doesn't appear to be a header change description. Ignore it.
74  continue;
75  }
76 
77  // Only keep files that properly parse.
78  TUs.push_back(TU);
79  }
80 
81  return ErrorCode;
82 }
83 
85  const llvm::StringRef Directory, TUDiagnostics &TUs,
86  TUReplacementFiles &TUFiles, 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 /// Extract replacements from collected TranslationUnitReplacements and
129 /// TranslationUnitDiagnostics and group them per file. Identical replacements
130 /// from diagnostics are deduplicated.
131 ///
132 /// \param[in] TUs Collection of all found and deserialized
133 /// TranslationUnitReplacements.
134 /// \param[in] TUDs Collection of all found and deserialized
135 /// TranslationUnitDiagnostics.
136 /// \param[in] SM Used to deduplicate paths.
137 ///
138 /// \returns A map mapping FileEntry to a set of Replacement targeting that
139 /// file.
140 static llvm::DenseMap<const FileEntry *, std::vector<tooling::Replacement>>
142  const clang::SourceManager &SM) {
143  std::set<StringRef> Warned;
144  llvm::DenseMap<const FileEntry *, std::vector<tooling::Replacement>>
145  GroupedReplacements;
146 
147  // Deduplicate identical replacements in diagnostics unless they are from the
148  // same TU.
149  // FIXME: Find an efficient way to deduplicate on diagnostics level.
150  llvm::DenseMap<const FileEntry *,
151  std::map<tooling::Replacement,
152  const tooling::TranslationUnitDiagnostics *>>
153  DiagReplacements;
154 
155  auto AddToGroup = [&](const tooling::Replacement &R,
156  const tooling::TranslationUnitDiagnostics *SourceTU,
157  const llvm::Optional<std::string> BuildDir) {
158  // Use the file manager to deduplicate paths. FileEntries are
159  // automatically canonicalized.
160  auto PrevWorkingDir = SM.getFileManager().getFileSystemOpts().WorkingDir;
161  if (BuildDir)
162  SM.getFileManager().getFileSystemOpts().WorkingDir = std::move(*BuildDir);
163  if (auto Entry = SM.getFileManager().getFile(R.getFilePath())) {
164  if (SourceTU) {
165  auto &Replaces = DiagReplacements[*Entry];
166  auto It = Replaces.find(R);
167  if (It == Replaces.end())
168  Replaces.emplace(R, SourceTU);
169  else if (It->second != SourceTU)
170  // This replacement is a duplicate of one suggested by another TU.
171  return;
172  }
173  GroupedReplacements[*Entry].push_back(R);
174  } else if (Warned.insert(R.getFilePath()).second) {
175  errs() << "Described file '" << R.getFilePath()
176  << "' doesn't exist. Ignoring...\n";
177  }
178  SM.getFileManager().getFileSystemOpts().WorkingDir = PrevWorkingDir;
179  };
180 
181  for (const auto &TU : TUs)
182  for (const tooling::Replacement &R : TU.Replacements)
183  AddToGroup(R, nullptr, {});
184 
185  for (const auto &TU : TUDs)
186  for (const auto &D : TU.Diagnostics)
187  if (const auto *ChoosenFix = tooling::selectFirstFix(D)) {
188  for (const auto &Fix : *ChoosenFix)
189  for (const tooling::Replacement &R : Fix.second)
190  AddToGroup(R, &TU, D.BuildDirectory);
191  }
192 
193  // Sort replacements per file to keep consistent behavior when
194  // clang-apply-replacements run on differents machine.
195  for (auto &FileAndReplacements : GroupedReplacements) {
196  llvm::sort(FileAndReplacements.second.begin(),
197  FileAndReplacements.second.end());
198  }
199 
200  return GroupedReplacements;
201 }
202 
203 bool mergeAndDeduplicate(const TUReplacements &TUs, const TUDiagnostics &TUDs,
204  FileToChangesMap &FileChanges,
205  clang::SourceManager &SM) {
206  auto GroupedReplacements = groupReplacements(TUs, TUDs, SM);
207  bool ConflictDetected = false;
208 
209  // To report conflicting replacements on corresponding file, all replacements
210  // are stored into 1 big AtomicChange.
211  for (const auto &FileAndReplacements : GroupedReplacements) {
212  const FileEntry *Entry = FileAndReplacements.first;
213  const SourceLocation BeginLoc =
214  SM.getLocForStartOfFile(SM.getOrCreateFileID(Entry, SrcMgr::C_User));
215  tooling::AtomicChange FileChange(Entry->getName(), Entry->getName());
216  for (const auto &R : FileAndReplacements.second) {
217  llvm::Error Err =
218  FileChange.replace(SM, BeginLoc.getLocWithOffset(R.getOffset()),
219  R.getLength(), R.getReplacementText());
220  if (Err) {
221  // FIXME: This will report conflicts by pair using a file+offset format
222  // which is not so much human readable.
223  // A first improvement could be to translate offset to line+col. For
224  // this and without loosing error message some modifications around
225  // `tooling::ReplacementError` are need (access to
226  // `getReplacementErrString`).
227  // A better strategy could be to add a pretty printer methods for
228  // conflict reporting. Methods that could be parameterized to report a
229  // conflict in different format, file+offset, file+line+col, or even
230  // more human readable using VCS conflict markers.
231  // For now, printing directly the error reported by `AtomicChange` is
232  // the easiest solution.
233  errs() << llvm::toString(std::move(Err)) << "\n";
234  ConflictDetected = true;
235  }
236  }
237  FileChanges.try_emplace(Entry,
238  std::vector<tooling::AtomicChange>{FileChange});
239  }
240 
241  return !ConflictDetected;
242 }
243 
244 llvm::Expected<std::string>
245 applyChanges(StringRef File, const std::vector<tooling::AtomicChange> &Changes,
246  const tooling::ApplyChangesSpec &Spec,
247  DiagnosticsEngine &Diagnostics) {
248  FileManager Files((FileSystemOptions()));
249  SourceManager SM(Diagnostics, Files);
250 
251  llvm::ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
252  SM.getFileManager().getBufferForFile(File);
253  if (!Buffer)
254  return errorCodeToError(Buffer.getError());
255  return tooling::applyAtomicChanges(File, Buffer.get()->getBuffer(), Changes,
256  Spec);
257 }
258 
260  clang::DiagnosticsEngine &Diagnostics) {
261  bool Success = true;
262  for (const auto &Filename : Files) {
263  std::error_code Error = llvm::sys::fs::remove(Filename);
264  if (Error) {
265  Success = false;
266  // FIXME: Use Diagnostics for outputting errors.
267  errs() << "Error deleting file: " << Filename << "\n";
268  errs() << Error.message() << "\n";
269  errs() << "Please delete the file manually\n";
270  }
271  }
272  return Success;
273 }
274 
275 } // end namespace replace
276 } // end namespace clang
llvm
Some operations such as code completion produce a set of candidates.
Definition: YAMLGenerator.cpp:28
clang::replace::FileToChangesMap
llvm::DenseMap< const clang::FileEntry *, std::vector< tooling::AtomicChange > > FileToChangesMap
Map mapping file name to a set of AtomicChange targeting that file.
Definition: ApplyReplacements.h:45
E
const Expr * E
Definition: AvoidBindCheck.cpp:88
Diagnostics
WantDiagnostics Diagnostics
Definition: TUScheduler.cpp:602
Filename
std::string Filename
Filename as a string.
Definition: IncludeOrderCheck.cpp:39
clang::replace::TUReplacements
std::vector< clang::tooling::TranslationUnitReplacements > TUReplacements
Collection of TranslationUnitReplacements.
Definition: ApplyReplacements.h:34
eatDiagnostics
static void eatDiagnostics(const SMDiagnostic &, void *)
Definition: ApplyReplacements.cpp:35
Changes
tooling::Replacements Changes
Definition: Format.cpp:109
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::clangd::ErrorCode
ErrorCode
Definition: Protocol.h:47
clang::replace::deleteReplacementFiles
bool deleteReplacementFiles(const TUReplacementFiles &Files, clang::DiagnosticsEngine &Diagnostics)
Delete the replacement files.
Definition: ApplyReplacements.cpp:259
ns1::ns2::D
@ D
Definition: CategoricalFeature.h:3
clang::replace::applyChanges
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.
Definition: ApplyReplacements.cpp:245
Directory
llvm::StringRef Directory
Definition: Serialization.cpp:420
clang::clangd::replace
static std::string replace(llvm::StringRef Haystack, llvm::StringRef Needle, llvm::StringRef Repl)
Definition: TestIndex.cpp:30
clang::replace::TUDiagnostics
std::vector< clang::tooling::TranslationUnitDiagnostics > TUDiagnostics
Collection of TranslationUniDiagnostics.
Definition: ApplyReplacements.h:40
Files
llvm::DenseSet< FileID > Files
Definition: IncludeCleaner.cpp:194
Entry
Definition: Modularize.cpp:428
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:40
clang::replace::groupReplacements
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...
Definition: ApplyReplacements.cpp:141
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::cppcoreguidelines::toString
static llvm::StringRef toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)
Definition: SpecialMemberFunctionsCheck.cpp:55
Out
CompiledFragmentImpl & Out
Definition: ConfigCompile.cpp:99
clang::replace::mergeAndDeduplicate
bool mergeAndDeduplicate(const TUReplacements &TUs, const TUDiagnostics &TUDs, FileToChangesMap &FileChanges, clang::SourceManager &SM)
Deduplicate, check for conflicts, and extract all Replacements stored in TUs.
Definition: ApplyReplacements.cpp:203
ApplyReplacements.h
clang::replace::TUReplacementFiles
std::vector< std::string > TUReplacementFiles
Collection of TranslationUnitReplacement files.
Definition: ApplyReplacements.h:37