annotate clang-tools-extra/clang-apply-replacements/lib/Tooling/ApplyReplacements.cpp @ 173:0572611fdcc8 llvm10 llvm12

reorgnization done
author Shinji KONO <kono@ie.u-ryukyu.ac.jp>
date Mon, 25 May 2020 11:55:54 +0900
parents 1d019706d866
children c4bab56944e8
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
150
anatofuz
parents:
diff changeset
1 //===-- ApplyReplacements.cpp - Apply and deduplicate replacements --------===//
anatofuz
parents:
diff changeset
2 //
anatofuz
parents:
diff changeset
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
anatofuz
parents:
diff changeset
4 // See https://llvm.org/LICENSE.txt for license information.
anatofuz
parents:
diff changeset
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
anatofuz
parents:
diff changeset
6 //
anatofuz
parents:
diff changeset
7 //===----------------------------------------------------------------------===//
anatofuz
parents:
diff changeset
8 ///
anatofuz
parents:
diff changeset
9 /// \file
anatofuz
parents:
diff changeset
10 /// This file provides the implementation for deduplicating, detecting
anatofuz
parents:
diff changeset
11 /// conflicts in, and applying collections of Replacements.
anatofuz
parents:
diff changeset
12 ///
anatofuz
parents:
diff changeset
13 /// FIXME: Use Diagnostics for output instead of llvm::errs().
anatofuz
parents:
diff changeset
14 ///
anatofuz
parents:
diff changeset
15 //===----------------------------------------------------------------------===//
anatofuz
parents:
diff changeset
16 #include "clang-apply-replacements/Tooling/ApplyReplacements.h"
anatofuz
parents:
diff changeset
17 #include "clang/Basic/LangOptions.h"
anatofuz
parents:
diff changeset
18 #include "clang/Basic/SourceManager.h"
anatofuz
parents:
diff changeset
19 #include "clang/Format/Format.h"
anatofuz
parents:
diff changeset
20 #include "clang/Lex/Lexer.h"
anatofuz
parents:
diff changeset
21 #include "clang/Rewrite/Core/Rewriter.h"
anatofuz
parents:
diff changeset
22 #include "clang/Tooling/Core/Diagnostic.h"
anatofuz
parents:
diff changeset
23 #include "clang/Tooling/DiagnosticsYaml.h"
anatofuz
parents:
diff changeset
24 #include "clang/Tooling/ReplacementsYaml.h"
anatofuz
parents:
diff changeset
25 #include "llvm/ADT/ArrayRef.h"
anatofuz
parents:
diff changeset
26 #include "llvm/Support/FileSystem.h"
anatofuz
parents:
diff changeset
27 #include "llvm/Support/MemoryBuffer.h"
anatofuz
parents:
diff changeset
28 #include "llvm/Support/Path.h"
anatofuz
parents:
diff changeset
29 #include "llvm/Support/raw_ostream.h"
anatofuz
parents:
diff changeset
30
anatofuz
parents:
diff changeset
31 using namespace llvm;
anatofuz
parents:
diff changeset
32 using namespace clang;
anatofuz
parents:
diff changeset
33
anatofuz
parents:
diff changeset
34 static void eatDiagnostics(const SMDiagnostic &, void *) {}
anatofuz
parents:
diff changeset
35
anatofuz
parents:
diff changeset
36 namespace clang {
anatofuz
parents:
diff changeset
37 namespace replace {
anatofuz
parents:
diff changeset
38
anatofuz
parents:
diff changeset
39 std::error_code collectReplacementsFromDirectory(
anatofuz
parents:
diff changeset
40 const llvm::StringRef Directory, TUReplacements &TUs,
anatofuz
parents:
diff changeset
41 TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {
anatofuz
parents:
diff changeset
42 using namespace llvm::sys::fs;
anatofuz
parents:
diff changeset
43 using namespace llvm::sys::path;
anatofuz
parents:
diff changeset
44
anatofuz
parents:
diff changeset
45 std::error_code ErrorCode;
anatofuz
parents:
diff changeset
46
anatofuz
parents:
diff changeset
47 for (recursive_directory_iterator I(Directory, ErrorCode), E;
anatofuz
parents:
diff changeset
48 I != E && !ErrorCode; I.increment(ErrorCode)) {
anatofuz
parents:
diff changeset
49 if (filename(I->path())[0] == '.') {
anatofuz
parents:
diff changeset
50 // Indicate not to descend into directories beginning with '.'
anatofuz
parents:
diff changeset
51 I.no_push();
anatofuz
parents:
diff changeset
52 continue;
anatofuz
parents:
diff changeset
53 }
anatofuz
parents:
diff changeset
54
anatofuz
parents:
diff changeset
55 if (extension(I->path()) != ".yaml")
anatofuz
parents:
diff changeset
56 continue;
anatofuz
parents:
diff changeset
57
anatofuz
parents:
diff changeset
58 TUFiles.push_back(I->path());
anatofuz
parents:
diff changeset
59
anatofuz
parents:
diff changeset
60 ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
anatofuz
parents:
diff changeset
61 MemoryBuffer::getFile(I->path());
anatofuz
parents:
diff changeset
62 if (std::error_code BufferError = Out.getError()) {
anatofuz
parents:
diff changeset
63 errs() << "Error reading " << I->path() << ": " << BufferError.message()
anatofuz
parents:
diff changeset
64 << "\n";
anatofuz
parents:
diff changeset
65 continue;
anatofuz
parents:
diff changeset
66 }
anatofuz
parents:
diff changeset
67
anatofuz
parents:
diff changeset
68 yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
anatofuz
parents:
diff changeset
69 tooling::TranslationUnitReplacements TU;
anatofuz
parents:
diff changeset
70 YIn >> TU;
anatofuz
parents:
diff changeset
71 if (YIn.error()) {
anatofuz
parents:
diff changeset
72 // File doesn't appear to be a header change description. Ignore it.
anatofuz
parents:
diff changeset
73 continue;
anatofuz
parents:
diff changeset
74 }
anatofuz
parents:
diff changeset
75
anatofuz
parents:
diff changeset
76 // Only keep files that properly parse.
anatofuz
parents:
diff changeset
77 TUs.push_back(TU);
anatofuz
parents:
diff changeset
78 }
anatofuz
parents:
diff changeset
79
anatofuz
parents:
diff changeset
80 return ErrorCode;
anatofuz
parents:
diff changeset
81 }
anatofuz
parents:
diff changeset
82
anatofuz
parents:
diff changeset
83 std::error_code collectReplacementsFromDirectory(
anatofuz
parents:
diff changeset
84 const llvm::StringRef Directory, TUDiagnostics &TUs,
anatofuz
parents:
diff changeset
85 TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {
anatofuz
parents:
diff changeset
86 using namespace llvm::sys::fs;
anatofuz
parents:
diff changeset
87 using namespace llvm::sys::path;
anatofuz
parents:
diff changeset
88
anatofuz
parents:
diff changeset
89 std::error_code ErrorCode;
anatofuz
parents:
diff changeset
90
anatofuz
parents:
diff changeset
91 for (recursive_directory_iterator I(Directory, ErrorCode), E;
anatofuz
parents:
diff changeset
92 I != E && !ErrorCode; I.increment(ErrorCode)) {
anatofuz
parents:
diff changeset
93 if (filename(I->path())[0] == '.') {
anatofuz
parents:
diff changeset
94 // Indicate not to descend into directories beginning with '.'
anatofuz
parents:
diff changeset
95 I.no_push();
anatofuz
parents:
diff changeset
96 continue;
anatofuz
parents:
diff changeset
97 }
anatofuz
parents:
diff changeset
98
anatofuz
parents:
diff changeset
99 if (extension(I->path()) != ".yaml")
anatofuz
parents:
diff changeset
100 continue;
anatofuz
parents:
diff changeset
101
anatofuz
parents:
diff changeset
102 TUFiles.push_back(I->path());
anatofuz
parents:
diff changeset
103
anatofuz
parents:
diff changeset
104 ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
anatofuz
parents:
diff changeset
105 MemoryBuffer::getFile(I->path());
anatofuz
parents:
diff changeset
106 if (std::error_code BufferError = Out.getError()) {
anatofuz
parents:
diff changeset
107 errs() << "Error reading " << I->path() << ": " << BufferError.message()
anatofuz
parents:
diff changeset
108 << "\n";
anatofuz
parents:
diff changeset
109 continue;
anatofuz
parents:
diff changeset
110 }
anatofuz
parents:
diff changeset
111
anatofuz
parents:
diff changeset
112 yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
anatofuz
parents:
diff changeset
113 tooling::TranslationUnitDiagnostics TU;
anatofuz
parents:
diff changeset
114 YIn >> TU;
anatofuz
parents:
diff changeset
115 if (YIn.error()) {
anatofuz
parents:
diff changeset
116 // File doesn't appear to be a header change description. Ignore it.
anatofuz
parents:
diff changeset
117 continue;
anatofuz
parents:
diff changeset
118 }
anatofuz
parents:
diff changeset
119
anatofuz
parents:
diff changeset
120 // Only keep files that properly parse.
anatofuz
parents:
diff changeset
121 TUs.push_back(TU);
anatofuz
parents:
diff changeset
122 }
anatofuz
parents:
diff changeset
123
anatofuz
parents:
diff changeset
124 return ErrorCode;
anatofuz
parents:
diff changeset
125 }
anatofuz
parents:
diff changeset
126
anatofuz
parents:
diff changeset
127 /// Extract replacements from collected TranslationUnitReplacements and
anatofuz
parents:
diff changeset
128 /// TranslationUnitDiagnostics and group them per file. Identical replacements
anatofuz
parents:
diff changeset
129 /// from diagnostics are deduplicated.
anatofuz
parents:
diff changeset
130 ///
anatofuz
parents:
diff changeset
131 /// \param[in] TUs Collection of all found and deserialized
anatofuz
parents:
diff changeset
132 /// TranslationUnitReplacements.
anatofuz
parents:
diff changeset
133 /// \param[in] TUDs Collection of all found and deserialized
anatofuz
parents:
diff changeset
134 /// TranslationUnitDiagnostics.
anatofuz
parents:
diff changeset
135 /// \param[in] SM Used to deduplicate paths.
anatofuz
parents:
diff changeset
136 ///
anatofuz
parents:
diff changeset
137 /// \returns A map mapping FileEntry to a set of Replacement targeting that
anatofuz
parents:
diff changeset
138 /// file.
anatofuz
parents:
diff changeset
139 static llvm::DenseMap<const FileEntry *, std::vector<tooling::Replacement>>
anatofuz
parents:
diff changeset
140 groupReplacements(const TUReplacements &TUs, const TUDiagnostics &TUDs,
anatofuz
parents:
diff changeset
141 const clang::SourceManager &SM) {
anatofuz
parents:
diff changeset
142 std::set<StringRef> Warned;
anatofuz
parents:
diff changeset
143 llvm::DenseMap<const FileEntry *, std::vector<tooling::Replacement>>
anatofuz
parents:
diff changeset
144 GroupedReplacements;
anatofuz
parents:
diff changeset
145
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
146 // Deduplicate identical replacements in diagnostics unless they are from the
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
147 // same TU.
150
anatofuz
parents:
diff changeset
148 // FIXME: Find an efficient way to deduplicate on diagnostics level.
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
149 llvm::DenseMap<const FileEntry *,
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
150 std::map<tooling::Replacement,
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
151 const tooling::TranslationUnitDiagnostics *>>
150
anatofuz
parents:
diff changeset
152 DiagReplacements;
anatofuz
parents:
diff changeset
153
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
154 auto AddToGroup = [&](const tooling::Replacement &R,
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
155 const tooling::TranslationUnitDiagnostics *SourceTU) {
150
anatofuz
parents:
diff changeset
156 // Use the file manager to deduplicate paths. FileEntries are
anatofuz
parents:
diff changeset
157 // automatically canonicalized.
anatofuz
parents:
diff changeset
158 if (auto Entry = SM.getFileManager().getFile(R.getFilePath())) {
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
159 if (SourceTU) {
150
anatofuz
parents:
diff changeset
160 auto &Replaces = DiagReplacements[*Entry];
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
161 auto It = Replaces.find(R);
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
162 if (It == Replaces.end())
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
163 Replaces.emplace(R, SourceTU);
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
164 else if (It->second != SourceTU)
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
165 // This replacement is a duplicate of one suggested by another TU.
150
anatofuz
parents:
diff changeset
166 return;
anatofuz
parents:
diff changeset
167 }
anatofuz
parents:
diff changeset
168 GroupedReplacements[*Entry].push_back(R);
anatofuz
parents:
diff changeset
169 } else if (Warned.insert(R.getFilePath()).second) {
anatofuz
parents:
diff changeset
170 errs() << "Described file '" << R.getFilePath()
anatofuz
parents:
diff changeset
171 << "' doesn't exist. Ignoring...\n";
anatofuz
parents:
diff changeset
172 }
anatofuz
parents:
diff changeset
173 };
anatofuz
parents:
diff changeset
174
anatofuz
parents:
diff changeset
175 for (const auto &TU : TUs)
anatofuz
parents:
diff changeset
176 for (const tooling::Replacement &R : TU.Replacements)
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
177 AddToGroup(R, nullptr);
150
anatofuz
parents:
diff changeset
178
anatofuz
parents:
diff changeset
179 for (const auto &TU : TUDs)
anatofuz
parents:
diff changeset
180 for (const auto &D : TU.Diagnostics)
anatofuz
parents:
diff changeset
181 if (const auto *ChoosenFix = tooling::selectFirstFix(D)) {
anatofuz
parents:
diff changeset
182 for (const auto &Fix : *ChoosenFix)
anatofuz
parents:
diff changeset
183 for (const tooling::Replacement &R : Fix.second)
173
0572611fdcc8 reorgnization done
Shinji KONO <kono@ie.u-ryukyu.ac.jp>
parents: 150
diff changeset
184 AddToGroup(R, &TU);
150
anatofuz
parents:
diff changeset
185 }
anatofuz
parents:
diff changeset
186
anatofuz
parents:
diff changeset
187 // Sort replacements per file to keep consistent behavior when
anatofuz
parents:
diff changeset
188 // clang-apply-replacements run on differents machine.
anatofuz
parents:
diff changeset
189 for (auto &FileAndReplacements : GroupedReplacements) {
anatofuz
parents:
diff changeset
190 llvm::sort(FileAndReplacements.second.begin(),
anatofuz
parents:
diff changeset
191 FileAndReplacements.second.end());
anatofuz
parents:
diff changeset
192 }
anatofuz
parents:
diff changeset
193
anatofuz
parents:
diff changeset
194 return GroupedReplacements;
anatofuz
parents:
diff changeset
195 }
anatofuz
parents:
diff changeset
196
anatofuz
parents:
diff changeset
197 bool mergeAndDeduplicate(const TUReplacements &TUs, const TUDiagnostics &TUDs,
anatofuz
parents:
diff changeset
198 FileToChangesMap &FileChanges,
anatofuz
parents:
diff changeset
199 clang::SourceManager &SM) {
anatofuz
parents:
diff changeset
200 auto GroupedReplacements = groupReplacements(TUs, TUDs, SM);
anatofuz
parents:
diff changeset
201 bool ConflictDetected = false;
anatofuz
parents:
diff changeset
202
anatofuz
parents:
diff changeset
203 // To report conflicting replacements on corresponding file, all replacements
anatofuz
parents:
diff changeset
204 // are stored into 1 big AtomicChange.
anatofuz
parents:
diff changeset
205 for (const auto &FileAndReplacements : GroupedReplacements) {
anatofuz
parents:
diff changeset
206 const FileEntry *Entry = FileAndReplacements.first;
anatofuz
parents:
diff changeset
207 const SourceLocation BeginLoc =
anatofuz
parents:
diff changeset
208 SM.getLocForStartOfFile(SM.getOrCreateFileID(Entry, SrcMgr::C_User));
anatofuz
parents:
diff changeset
209 tooling::AtomicChange FileChange(Entry->getName(), Entry->getName());
anatofuz
parents:
diff changeset
210 for (const auto &R : FileAndReplacements.second) {
anatofuz
parents:
diff changeset
211 llvm::Error Err =
anatofuz
parents:
diff changeset
212 FileChange.replace(SM, BeginLoc.getLocWithOffset(R.getOffset()),
anatofuz
parents:
diff changeset
213 R.getLength(), R.getReplacementText());
anatofuz
parents:
diff changeset
214 if (Err) {
anatofuz
parents:
diff changeset
215 // FIXME: This will report conflicts by pair using a file+offset format
anatofuz
parents:
diff changeset
216 // which is not so much human readable.
anatofuz
parents:
diff changeset
217 // A first improvement could be to translate offset to line+col. For
anatofuz
parents:
diff changeset
218 // this and without loosing error message some modifications around
anatofuz
parents:
diff changeset
219 // `tooling::ReplacementError` are need (access to
anatofuz
parents:
diff changeset
220 // `getReplacementErrString`).
anatofuz
parents:
diff changeset
221 // A better strategy could be to add a pretty printer methods for
anatofuz
parents:
diff changeset
222 // conflict reporting. Methods that could be parameterized to report a
anatofuz
parents:
diff changeset
223 // conflict in different format, file+offset, file+line+col, or even
anatofuz
parents:
diff changeset
224 // more human readable using VCS conflict markers.
anatofuz
parents:
diff changeset
225 // For now, printing directly the error reported by `AtomicChange` is
anatofuz
parents:
diff changeset
226 // the easiest solution.
anatofuz
parents:
diff changeset
227 errs() << llvm::toString(std::move(Err)) << "\n";
anatofuz
parents:
diff changeset
228 ConflictDetected = true;
anatofuz
parents:
diff changeset
229 }
anatofuz
parents:
diff changeset
230 }
anatofuz
parents:
diff changeset
231 FileChanges.try_emplace(Entry,
anatofuz
parents:
diff changeset
232 std::vector<tooling::AtomicChange>{FileChange});
anatofuz
parents:
diff changeset
233 }
anatofuz
parents:
diff changeset
234
anatofuz
parents:
diff changeset
235 return !ConflictDetected;
anatofuz
parents:
diff changeset
236 }
anatofuz
parents:
diff changeset
237
anatofuz
parents:
diff changeset
238 llvm::Expected<std::string>
anatofuz
parents:
diff changeset
239 applyChanges(StringRef File, const std::vector<tooling::AtomicChange> &Changes,
anatofuz
parents:
diff changeset
240 const tooling::ApplyChangesSpec &Spec,
anatofuz
parents:
diff changeset
241 DiagnosticsEngine &Diagnostics) {
anatofuz
parents:
diff changeset
242 FileManager Files((FileSystemOptions()));
anatofuz
parents:
diff changeset
243 SourceManager SM(Diagnostics, Files);
anatofuz
parents:
diff changeset
244
anatofuz
parents:
diff changeset
245 llvm::ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
anatofuz
parents:
diff changeset
246 SM.getFileManager().getBufferForFile(File);
anatofuz
parents:
diff changeset
247 if (!Buffer)
anatofuz
parents:
diff changeset
248 return errorCodeToError(Buffer.getError());
anatofuz
parents:
diff changeset
249 return tooling::applyAtomicChanges(File, Buffer.get()->getBuffer(), Changes,
anatofuz
parents:
diff changeset
250 Spec);
anatofuz
parents:
diff changeset
251 }
anatofuz
parents:
diff changeset
252
anatofuz
parents:
diff changeset
253 bool deleteReplacementFiles(const TUReplacementFiles &Files,
anatofuz
parents:
diff changeset
254 clang::DiagnosticsEngine &Diagnostics) {
anatofuz
parents:
diff changeset
255 bool Success = true;
anatofuz
parents:
diff changeset
256 for (const auto &Filename : Files) {
anatofuz
parents:
diff changeset
257 std::error_code Error = llvm::sys::fs::remove(Filename);
anatofuz
parents:
diff changeset
258 if (Error) {
anatofuz
parents:
diff changeset
259 Success = false;
anatofuz
parents:
diff changeset
260 // FIXME: Use Diagnostics for outputting errors.
anatofuz
parents:
diff changeset
261 errs() << "Error deleting file: " << Filename << "\n";
anatofuz
parents:
diff changeset
262 errs() << Error.message() << "\n";
anatofuz
parents:
diff changeset
263 errs() << "Please delete the file manually\n";
anatofuz
parents:
diff changeset
264 }
anatofuz
parents:
diff changeset
265 }
anatofuz
parents:
diff changeset
266 return Success;
anatofuz
parents:
diff changeset
267 }
anatofuz
parents:
diff changeset
268
anatofuz
parents:
diff changeset
269 } // end namespace replace
anatofuz
parents:
diff changeset
270 } // end namespace clang