clang-tools 10.0.0

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 //===----------------------------------------------------------------------===//
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 /// 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 around
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
llvm
Some operations such as code completion produce a set of candidates.
Definition: YAMLGenerator.cpp:28
Changes
tooling::Replacements Changes
Definition: Format.cpp:108
clang::replace::deleteReplacementFiles
bool deleteReplacementFiles(const TUReplacementFiles &Files, clang::DiagnosticsEngine &Diagnostics)
Delete the replacement files.
Definition: ApplyReplacements.cpp:245
clang::replace::TUReplacements
std::vector< clang::tooling::TranslationUnitReplacements > TUReplacements
Collection of TranslationUnitReplacements.
Definition: ApplyReplacements.h:35
clang::tidy::cppcoreguidelines::toString
static llvm::StringRef toString(SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K)
Definition: SpecialMemberFunctionsCheck.cpp:58
eatDiagnostics
static void eatDiagnostics(const SMDiagnostic &, void *)
Definition: ApplyReplacements.cpp:34
clang::clangd::replace
static std::string replace(llvm::StringRef Haystack, llvm::StringRef Needle, llvm::StringRef Repl)
Definition: TestIndex.cpp:30
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:140
clang::clangd::ErrorCode
ErrorCode
Definition: Protocol.h:40
Filename
std::string Filename
Filename as a string.
Definition: IncludeOrderCheck.cpp:39
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:231
Directory
llvm::StringRef Directory
Definition: Serialization.cpp:387
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:39
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
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:189
E
const Expr * E
Definition: AvoidBindCheck.cpp:82
ApplyReplacements.h
This file provides the interface for deduplicating, detecting conflicts in, and applying collections ...
clang::replace::TUDiagnostics
std::vector< clang::tooling::TranslationUnitDiagnostics > TUDiagnostics
Collection of TranslationUniDiagnostics.
Definition: ApplyReplacements.h:41
clang::replace::TUReplacementFiles
std::vector< std::string > TUReplacementFiles
Collection of TranslationUnitReplacement files.
Definition: ApplyReplacements.h:38
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::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:46
Files
llvm::StringMap< std::string > Files
Definition: TUSchedulerTests.cpp:119
Entry
Definition: Modularize.cpp:429