1 //===--- AtomicChange.cpp - AtomicChange implementation -----------------*- C++ -*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "clang/Tooling/Refactoring/AtomicChange.h"
11 #include "clang/Tooling/ReplacementsYaml.h"
12 #include "llvm/Support/YAMLTraits.h"
13 #include <string>
14 
15 LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(std::string)
16 LLVM_YAML_IS_SEQUENCE_VECTOR(clang::tooling::AtomicChange)
17 
18 namespace {
19 /// \brief Helper to (de)serialize an AtomicChange since we don't have direct
20 /// access to its data members.
21 /// Data members of a normalized AtomicChange can be directly mapped from/to
22 /// YAML string.
23 struct NormalizedAtomicChange {
24   NormalizedAtomicChange() = default;
25 
26   NormalizedAtomicChange(const llvm::yaml::IO &) {}
27 
28   // This converts AtomicChange's internal implementation of the replacements
29   // set to a vector of replacements.
30   NormalizedAtomicChange(const llvm::yaml::IO &,
31                          const clang::tooling::AtomicChange &E)
32       : Key(E.getKey()), FilePath(E.getFilePath()), Error(E.getError()),
33         InsertedHeaders(E.getInsertedHeaders()),
34         RemovedHeaders(E.getRemovedHeaders()),
35         Replaces(E.getReplacements().begin(), E.getReplacements().end()) {}
36 
37   // This is not expected to be called but needed for template instantiation.
38   clang::tooling::AtomicChange denormalize(const llvm::yaml::IO &) {
39     llvm_unreachable("Do not convert YAML to AtomicChange directly with '>>'. "
40                      "Use AtomicChange::convertFromYAML instead.");
41   }
42   std::string Key;
43   std::string FilePath;
44   std::string Error;
45   std::vector<std::string> InsertedHeaders;
46   std::vector<std::string> RemovedHeaders;
47   std::vector<clang::tooling::Replacement> Replaces;
48 };
49 } // anonymous namespace
50 
51 namespace llvm {
52 namespace yaml {
53 
54 /// \brief Specialized MappingTraits to describe how an AtomicChange is
55 /// (de)serialized.
56 template <> struct MappingTraits<NormalizedAtomicChange> {
57   static void mapping(IO &Io, NormalizedAtomicChange &Doc) {
58     Io.mapRequired("Key", Doc.Key);
59     Io.mapRequired("FilePath", Doc.FilePath);
60     Io.mapRequired("Error", Doc.Error);
61     Io.mapRequired("InsertedHeaders", Doc.InsertedHeaders);
62     Io.mapRequired("RemovedHeaders", Doc.RemovedHeaders);
63     Io.mapRequired("Replacements", Doc.Replaces);
64   }
65 };
66 
67 /// \brief Specialized MappingTraits to describe how an AtomicChange is
68 /// (de)serialized.
69 template <> struct MappingTraits<clang::tooling::AtomicChange> {
70   static void mapping(IO &Io, clang::tooling::AtomicChange &Doc) {
71     MappingNormalization<NormalizedAtomicChange, clang::tooling::AtomicChange>
72         Keys(Io, Doc);
73     Io.mapRequired("Key", Keys->Key);
74     Io.mapRequired("FilePath", Keys->FilePath);
75     Io.mapRequired("Error", Keys->Error);
76     Io.mapRequired("InsertedHeaders", Keys->InsertedHeaders);
77     Io.mapRequired("RemovedHeaders", Keys->RemovedHeaders);
78     Io.mapRequired("Replacements", Keys->Replaces);
79   }
80 };
81 
82 } // end namespace yaml
83 } // end namespace llvm
84 
85 namespace clang {
86 namespace tooling {
87 namespace {
88 
89 // Returns true if there is any line that violates \p ColumnLimit in range
90 // [Start, End].
91 bool violatesColumnLimit(llvm::StringRef Code, unsigned ColumnLimit,
92                          unsigned Start, unsigned End) {
93   auto StartPos = Code.rfind('\n', Start);
94   StartPos = (StartPos == llvm::StringRef::npos) ? 0 : StartPos + 1;
95 
96   auto EndPos = Code.find("\n", End);
97   if (EndPos == llvm::StringRef::npos)
98     EndPos = Code.size();
99 
100   llvm::SmallVector<llvm::StringRef, 8> Lines;
101   Code.substr(StartPos, EndPos - StartPos).split(Lines, '\n');
102   for (llvm::StringRef Line : Lines)
103     if (Line.size() > ColumnLimit)
104       return true;
105   return false;
106 }
107 
108 std::vector<Range>
109 getRangesForFormating(llvm::StringRef Code, unsigned ColumnLimit,
110                       ApplyChangesSpec::FormatOption Format,
111                       const clang::tooling::Replacements &Replaces) {
112   // kNone suppresses formatting entirely.
113   if (Format == ApplyChangesSpec::kNone)
114     return {};
115   std::vector<clang::tooling::Range> Ranges;
116   // This works assuming that replacements are ordered by offset.
117   // FIXME: use `getAffectedRanges()` to calculate when it does not include '\n'
118   // at the end of an insertion in affected ranges.
119   int Offset = 0;
120   for (const clang::tooling::Replacement &R : Replaces) {
121     int Start = R.getOffset() + Offset;
122     int End = Start + R.getReplacementText().size();
123     if (!R.getReplacementText().empty() &&
124         R.getReplacementText().back() == '\n' && R.getLength() == 0 &&
125         R.getOffset() > 0 && R.getOffset() <= Code.size() &&
126         Code[R.getOffset() - 1] == '\n')
127       // If we are inserting at the start of a line and the replacement ends in
128       // a newline, we don't need to format the subsequent line.
129       --End;
130     Offset += R.getReplacementText().size() - R.getLength();
131 
132     if (Format == ApplyChangesSpec::kAll ||
133         violatesColumnLimit(Code, ColumnLimit, Start, End))
134       Ranges.emplace_back(Start, End - Start);
135   }
136   return Ranges;
137 }
138 
139 inline llvm::Error make_string_error(const llvm::Twine &Message) {
140   return llvm::make_error<llvm::StringError>(Message,
141                                              llvm::inconvertibleErrorCode());
142 }
143 
144 // Creates replacements for inserting/deleting #include headers.
145 llvm::Expected<Replacements>
146 createReplacementsForHeaders(llvm::StringRef FilePath, llvm::StringRef Code,
147                              llvm::ArrayRef<AtomicChange> Changes,
148                              const format::FormatStyle &Style) {
149   // Create header insertion/deletion replacements to be cleaned up
150   // (i.e. converted to real insertion/deletion replacements).
151   Replacements HeaderReplacements;
152   for (const auto &Change : Changes) {
153     for (llvm::StringRef Header : Change.getInsertedHeaders()) {
154       std::string EscapedHeader =
155           Header.startswith("<") || Header.startswith("\"")
156               ? Header.str()
157               : ("\"" + Header + "\"").str();
158       std::string ReplacementText = "#include " + EscapedHeader;
159       // Offset UINT_MAX and length 0 indicate that the replacement is a header
160       // insertion.
161       llvm::Error Err = HeaderReplacements.add(
162           tooling::Replacement(FilePath, UINT_MAX, 0, ReplacementText));
163       if (Err)
164         return std::move(Err);
165     }
166     for (const std::string &Header : Change.getRemovedHeaders()) {
167       // Offset UINT_MAX and length 1 indicate that the replacement is a header
168       // deletion.
169       llvm::Error Err =
170           HeaderReplacements.add(Replacement(FilePath, UINT_MAX, 1, Header));
171       if (Err)
172         return std::move(Err);
173     }
174   }
175 
176   // cleanupAroundReplacements() converts header insertions/deletions into
177   // actual replacements that add/remove headers at the right location.
178   return clang::format::cleanupAroundReplacements(Code, HeaderReplacements,
179                                                   Style);
180 }
181 
182 // Combine replacements in all Changes as a `Replacements`. This ignores the
183 // file path in all replacements and replaces them with \p FilePath.
184 llvm::Expected<Replacements>
185 combineReplacementsInChanges(llvm::StringRef FilePath,
186                              llvm::ArrayRef<AtomicChange> Changes) {
187   Replacements Replaces;
188   for (const auto &Change : Changes)
189     for (const auto &R : Change.getReplacements())
190       if (auto Err = Replaces.add(Replacement(
191               FilePath, R.getOffset(), R.getLength(), R.getReplacementText())))
192         return std::move(Err);
193   return Replaces;
194 }
195 
196 } // end namespace
197 
198 AtomicChange::AtomicChange(const SourceManager &SM,
199                            SourceLocation KeyPosition) {
200   const FullSourceLoc FullKeyPosition(KeyPosition, SM);
201   std::pair<FileID, unsigned> FileIDAndOffset =
202       FullKeyPosition.getSpellingLoc().getDecomposedLoc();
203   const FileEntry *FE = SM.getFileEntryForID(FileIDAndOffset.first);
204   assert(FE && "Cannot create AtomicChange with invalid location.");
205   FilePath = FE->getName();
206   Key = FilePath + ":" + std::to_string(FileIDAndOffset.second);
207 }
208 
209 AtomicChange::AtomicChange(std::string Key, std::string FilePath,
210                            std::string Error,
211                            std::vector<std::string> InsertedHeaders,
212                            std::vector<std::string> RemovedHeaders,
213                            clang::tooling::Replacements Replaces)
214     : Key(std::move(Key)), FilePath(std::move(FilePath)),
215       Error(std::move(Error)), InsertedHeaders(std::move(InsertedHeaders)),
216       RemovedHeaders(std::move(RemovedHeaders)), Replaces(std::move(Replaces)) {
217 }
218 
219 std::string AtomicChange::toYAMLString() {
220   std::string YamlContent;
221   llvm::raw_string_ostream YamlContentStream(YamlContent);
222 
223   llvm::yaml::Output YAML(YamlContentStream);
224   YAML << *this;
225   YamlContentStream.flush();
226   return YamlContent;
227 }
228 
229 AtomicChange AtomicChange::convertFromYAML(llvm::StringRef YAMLContent) {
230   NormalizedAtomicChange NE;
231   llvm::yaml::Input YAML(YAMLContent);
232   YAML >> NE;
233   AtomicChange E(NE.Key, NE.FilePath, NE.Error, NE.InsertedHeaders,
234                  NE.RemovedHeaders, tooling::Replacements());
235   for (const auto &R : NE.Replaces) {
236     llvm::Error Err = E.Replaces.add(R);
237     if (Err)
238       llvm_unreachable(
239           "Failed to add replacement when Converting YAML to AtomicChange.");
240     llvm::consumeError(std::move(Err));
241   }
242   return E;
243 }
244 
245 llvm::Error AtomicChange::replace(const SourceManager &SM, SourceLocation Loc,
246                                   unsigned Length, llvm::StringRef Text) {
247   return Replaces.add(Replacement(SM, Loc, Length, Text));
248 }
249 
250 llvm::Error AtomicChange::insert(const SourceManager &SM, SourceLocation Loc,
251                                  llvm::StringRef Text, bool InsertAfter) {
252   if (Text.empty())
253     return llvm::Error::success();
254   Replacement R(SM, Loc, 0, Text);
255   llvm::Error Err = Replaces.add(R);
256   if (Err) {
257     return llvm::handleErrors(
258         std::move(Err), [&](const ReplacementError &RE) -> llvm::Error {
259           if (RE.get() != replacement_error::insert_conflict)
260             return llvm::make_error<ReplacementError>(RE);
261           unsigned NewOffset = Replaces.getShiftedCodePosition(R.getOffset());
262           if (!InsertAfter)
263             NewOffset -=
264                 RE.getExistingReplacement()->getReplacementText().size();
265           Replacement NewR(R.getFilePath(), NewOffset, 0, Text);
266           Replaces = Replaces.merge(Replacements(NewR));
267           return llvm::Error::success();
268         });
269   }
270   return llvm::Error::success();
271 }
272 
273 void AtomicChange::addHeader(llvm::StringRef Header) {
274   InsertedHeaders.push_back(Header);
275 }
276 
277 void AtomicChange::removeHeader(llvm::StringRef Header) {
278   RemovedHeaders.push_back(Header);
279 }
280 
281 llvm::Expected<std::string>
282 applyAtomicChanges(llvm::StringRef FilePath, llvm::StringRef Code,
283                    llvm::ArrayRef<AtomicChange> Changes,
284                    const ApplyChangesSpec &Spec) {
285   llvm::Expected<Replacements> HeaderReplacements =
286       createReplacementsForHeaders(FilePath, Code, Changes, Spec.Style);
287   if (!HeaderReplacements)
288     return make_string_error(
289         "Failed to create replacements for header changes: " +
290         llvm::toString(HeaderReplacements.takeError()));
291 
292   llvm::Expected<Replacements> Replaces =
293       combineReplacementsInChanges(FilePath, Changes);
294   if (!Replaces)
295     return make_string_error("Failed to combine replacements in all changes: " +
296                              llvm::toString(Replaces.takeError()));
297 
298   Replacements AllReplaces = std::move(*Replaces);
299   for (const auto &R : *HeaderReplacements) {
300     llvm::Error Err = AllReplaces.add(R);
301     if (Err)
302       return make_string_error(
303           "Failed to combine existing replacements with header replacements: " +
304           llvm::toString(std::move(Err)));
305   }
306 
307   if (Spec.Cleanup) {
308     llvm::Expected<Replacements> CleanReplaces =
309         format::cleanupAroundReplacements(Code, AllReplaces, Spec.Style);
310     if (!CleanReplaces)
311       return make_string_error("Failed to cleanup around replacements: " +
312                                llvm::toString(CleanReplaces.takeError()));
313     AllReplaces = std::move(*CleanReplaces);
314   }
315 
316   // Apply all replacements.
317   llvm::Expected<std::string> ChangedCode =
318       applyAllReplacements(Code, AllReplaces);
319   if (!ChangedCode)
320     return make_string_error("Failed to apply all replacements: " +
321                              llvm::toString(ChangedCode.takeError()));
322 
323   // Sort inserted headers. This is done even if other formatting is turned off
324   // as incorrectly sorted headers are always just wrong, it's not a matter of
325   // taste.
326   Replacements HeaderSortingReplacements = format::sortIncludes(
327       Spec.Style, *ChangedCode, AllReplaces.getAffectedRanges(), FilePath);
328   ChangedCode = applyAllReplacements(*ChangedCode, HeaderSortingReplacements);
329   if (!ChangedCode)
330     return make_string_error(
331         "Failed to apply replacements for sorting includes: " +
332         llvm::toString(ChangedCode.takeError()));
333 
334   AllReplaces = AllReplaces.merge(HeaderSortingReplacements);
335 
336   std::vector<Range> FormatRanges = getRangesForFormating(
337       *ChangedCode, Spec.Style.ColumnLimit, Spec.Format, AllReplaces);
338   if (!FormatRanges.empty()) {
339     Replacements FormatReplacements =
340         format::reformat(Spec.Style, *ChangedCode, FormatRanges, FilePath);
341     ChangedCode = applyAllReplacements(*ChangedCode, FormatReplacements);
342     if (!ChangedCode)
343       return make_string_error(
344           "Failed to apply replacements for formatting changed code: " +
345           llvm::toString(ChangedCode.takeError()));
346   }
347   return ChangedCode;
348 }
349 
350 } // end namespace tooling
351 } // end namespace clang
352