xref: /openbsd-src/gnu/llvm/clang/lib/Edit/EditedSource.cpp (revision 12c855180aad702bbcca06e0398d774beeafb155)
1e5dd7070Spatrick //===- EditedSource.cpp - Collection of source edits ----------------------===//
2e5dd7070Spatrick //
3e5dd7070Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e5dd7070Spatrick // See https://llvm.org/LICENSE.txt for license information.
5e5dd7070Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e5dd7070Spatrick //
7e5dd7070Spatrick //===----------------------------------------------------------------------===//
8e5dd7070Spatrick 
9e5dd7070Spatrick #include "clang/Edit/EditedSource.h"
10e5dd7070Spatrick #include "clang/Basic/CharInfo.h"
11e5dd7070Spatrick #include "clang/Basic/LLVM.h"
12e5dd7070Spatrick #include "clang/Basic/SourceLocation.h"
13e5dd7070Spatrick #include "clang/Basic/SourceManager.h"
14e5dd7070Spatrick #include "clang/Edit/Commit.h"
15e5dd7070Spatrick #include "clang/Edit/EditsReceiver.h"
16e5dd7070Spatrick #include "clang/Edit/FileOffset.h"
17e5dd7070Spatrick #include "clang/Lex/Lexer.h"
18e5dd7070Spatrick #include "llvm/ADT/STLExtras.h"
19e5dd7070Spatrick #include "llvm/ADT/SmallString.h"
20e5dd7070Spatrick #include "llvm/ADT/StringRef.h"
21e5dd7070Spatrick #include "llvm/ADT/Twine.h"
22e5dd7070Spatrick #include <algorithm>
23e5dd7070Spatrick #include <cassert>
24e5dd7070Spatrick #include <tuple>
25e5dd7070Spatrick #include <utility>
26e5dd7070Spatrick 
27e5dd7070Spatrick using namespace clang;
28e5dd7070Spatrick using namespace edit;
29e5dd7070Spatrick 
remove(CharSourceRange range)30e5dd7070Spatrick void EditsReceiver::remove(CharSourceRange range) {
31e5dd7070Spatrick   replace(range, StringRef());
32e5dd7070Spatrick }
33e5dd7070Spatrick 
deconstructMacroArgLoc(SourceLocation Loc,SourceLocation & ExpansionLoc,MacroArgUse & ArgUse)34e5dd7070Spatrick void EditedSource::deconstructMacroArgLoc(SourceLocation Loc,
35e5dd7070Spatrick                                           SourceLocation &ExpansionLoc,
36e5dd7070Spatrick                                           MacroArgUse &ArgUse) {
37e5dd7070Spatrick   assert(SourceMgr.isMacroArgExpansion(Loc));
38e5dd7070Spatrick   SourceLocation DefArgLoc =
39e5dd7070Spatrick       SourceMgr.getImmediateExpansionRange(Loc).getBegin();
40e5dd7070Spatrick   SourceLocation ImmediateExpansionLoc =
41e5dd7070Spatrick       SourceMgr.getImmediateExpansionRange(DefArgLoc).getBegin();
42e5dd7070Spatrick   ExpansionLoc = ImmediateExpansionLoc;
43e5dd7070Spatrick   while (SourceMgr.isMacroBodyExpansion(ExpansionLoc))
44e5dd7070Spatrick     ExpansionLoc =
45e5dd7070Spatrick         SourceMgr.getImmediateExpansionRange(ExpansionLoc).getBegin();
46e5dd7070Spatrick   SmallString<20> Buf;
47e5dd7070Spatrick   StringRef ArgName = Lexer::getSpelling(SourceMgr.getSpellingLoc(DefArgLoc),
48e5dd7070Spatrick                                          Buf, SourceMgr, LangOpts);
49e5dd7070Spatrick   ArgUse = MacroArgUse{nullptr, SourceLocation(), SourceLocation()};
50e5dd7070Spatrick   if (!ArgName.empty())
51e5dd7070Spatrick     ArgUse = {&IdentTable.get(ArgName), ImmediateExpansionLoc,
52e5dd7070Spatrick               SourceMgr.getSpellingLoc(DefArgLoc)};
53e5dd7070Spatrick }
54e5dd7070Spatrick 
startingCommit()55e5dd7070Spatrick void EditedSource::startingCommit() {}
56e5dd7070Spatrick 
finishedCommit()57e5dd7070Spatrick void EditedSource::finishedCommit() {
58e5dd7070Spatrick   for (auto &ExpArg : CurrCommitMacroArgExps) {
59e5dd7070Spatrick     SourceLocation ExpLoc;
60e5dd7070Spatrick     MacroArgUse ArgUse;
61e5dd7070Spatrick     std::tie(ExpLoc, ArgUse) = ExpArg;
62a9ac8606Spatrick     auto &ArgUses = ExpansionToArgMap[ExpLoc];
63*12c85518Srobert     if (!llvm::is_contained(ArgUses, ArgUse))
64e5dd7070Spatrick       ArgUses.push_back(ArgUse);
65e5dd7070Spatrick   }
66e5dd7070Spatrick   CurrCommitMacroArgExps.clear();
67e5dd7070Spatrick }
68e5dd7070Spatrick 
copyString(const Twine & twine)69e5dd7070Spatrick StringRef EditedSource::copyString(const Twine &twine) {
70e5dd7070Spatrick   SmallString<128> Data;
71e5dd7070Spatrick   return copyString(twine.toStringRef(Data));
72e5dd7070Spatrick }
73e5dd7070Spatrick 
canInsertInOffset(SourceLocation OrigLoc,FileOffset Offs)74e5dd7070Spatrick bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
75e5dd7070Spatrick   FileEditsTy::iterator FA = getActionForOffset(Offs);
76e5dd7070Spatrick   if (FA != FileEdits.end()) {
77e5dd7070Spatrick     if (FA->first != Offs)
78e5dd7070Spatrick       return false; // position has been removed.
79e5dd7070Spatrick   }
80e5dd7070Spatrick 
81e5dd7070Spatrick   if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
82e5dd7070Spatrick     SourceLocation ExpLoc;
83e5dd7070Spatrick     MacroArgUse ArgUse;
84e5dd7070Spatrick     deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);
85a9ac8606Spatrick     auto I = ExpansionToArgMap.find(ExpLoc);
86e5dd7070Spatrick     if (I != ExpansionToArgMap.end() &&
87*12c85518Srobert         llvm::any_of(I->second, [&](const MacroArgUse &U) {
88e5dd7070Spatrick           return ArgUse.Identifier == U.Identifier &&
89e5dd7070Spatrick                  std::tie(ArgUse.ImmediateExpansionLoc, ArgUse.UseLoc) !=
90e5dd7070Spatrick                      std::tie(U.ImmediateExpansionLoc, U.UseLoc);
91*12c85518Srobert         })) {
92e5dd7070Spatrick       // Trying to write in a macro argument input that has already been
93e5dd7070Spatrick       // written by a previous commit for another expansion of the same macro
94e5dd7070Spatrick       // argument name. For example:
95e5dd7070Spatrick       //
96e5dd7070Spatrick       // \code
97e5dd7070Spatrick       //   #define MAC(x) ((x)+(x))
98e5dd7070Spatrick       //   MAC(a)
99e5dd7070Spatrick       // \endcode
100e5dd7070Spatrick       //
101e5dd7070Spatrick       // A commit modified the macro argument 'a' due to the first '(x)'
102e5dd7070Spatrick       // expansion inside the macro definition, and a subsequent commit tried
103e5dd7070Spatrick       // to modify 'a' again for the second '(x)' expansion. The edits of the
104e5dd7070Spatrick       // second commit will be rejected.
105e5dd7070Spatrick       return false;
106e5dd7070Spatrick     }
107e5dd7070Spatrick   }
108e5dd7070Spatrick   return true;
109e5dd7070Spatrick }
110e5dd7070Spatrick 
commitInsert(SourceLocation OrigLoc,FileOffset Offs,StringRef text,bool beforePreviousInsertions)111e5dd7070Spatrick bool EditedSource::commitInsert(SourceLocation OrigLoc,
112e5dd7070Spatrick                                 FileOffset Offs, StringRef text,
113e5dd7070Spatrick                                 bool beforePreviousInsertions) {
114e5dd7070Spatrick   if (!canInsertInOffset(OrigLoc, Offs))
115e5dd7070Spatrick     return false;
116e5dd7070Spatrick   if (text.empty())
117e5dd7070Spatrick     return true;
118e5dd7070Spatrick 
119e5dd7070Spatrick   if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
120e5dd7070Spatrick     MacroArgUse ArgUse;
121e5dd7070Spatrick     SourceLocation ExpLoc;
122e5dd7070Spatrick     deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);
123e5dd7070Spatrick     if (ArgUse.Identifier)
124e5dd7070Spatrick       CurrCommitMacroArgExps.emplace_back(ExpLoc, ArgUse);
125e5dd7070Spatrick   }
126e5dd7070Spatrick 
127e5dd7070Spatrick   FileEdit &FA = FileEdits[Offs];
128e5dd7070Spatrick   if (FA.Text.empty()) {
129e5dd7070Spatrick     FA.Text = copyString(text);
130e5dd7070Spatrick     return true;
131e5dd7070Spatrick   }
132e5dd7070Spatrick 
133e5dd7070Spatrick   if (beforePreviousInsertions)
134e5dd7070Spatrick     FA.Text = copyString(Twine(text) + FA.Text);
135e5dd7070Spatrick   else
136e5dd7070Spatrick     FA.Text = copyString(Twine(FA.Text) + text);
137e5dd7070Spatrick 
138e5dd7070Spatrick   return true;
139e5dd7070Spatrick }
140e5dd7070Spatrick 
commitInsertFromRange(SourceLocation OrigLoc,FileOffset Offs,FileOffset InsertFromRangeOffs,unsigned Len,bool beforePreviousInsertions)141e5dd7070Spatrick bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
142e5dd7070Spatrick                                    FileOffset Offs,
143e5dd7070Spatrick                                    FileOffset InsertFromRangeOffs, unsigned Len,
144e5dd7070Spatrick                                    bool beforePreviousInsertions) {
145e5dd7070Spatrick   if (Len == 0)
146e5dd7070Spatrick     return true;
147e5dd7070Spatrick 
148e5dd7070Spatrick   SmallString<128> StrVec;
149e5dd7070Spatrick   FileOffset BeginOffs = InsertFromRangeOffs;
150e5dd7070Spatrick   FileOffset EndOffs = BeginOffs.getWithOffset(Len);
151e5dd7070Spatrick   FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
152e5dd7070Spatrick   if (I != FileEdits.begin())
153e5dd7070Spatrick     --I;
154e5dd7070Spatrick 
155e5dd7070Spatrick   for (; I != FileEdits.end(); ++I) {
156e5dd7070Spatrick     FileEdit &FA = I->second;
157e5dd7070Spatrick     FileOffset B = I->first;
158e5dd7070Spatrick     FileOffset E = B.getWithOffset(FA.RemoveLen);
159e5dd7070Spatrick 
160e5dd7070Spatrick     if (BeginOffs == B)
161e5dd7070Spatrick       break;
162e5dd7070Spatrick 
163e5dd7070Spatrick     if (BeginOffs < E) {
164e5dd7070Spatrick       if (BeginOffs > B) {
165e5dd7070Spatrick         BeginOffs = E;
166e5dd7070Spatrick         ++I;
167e5dd7070Spatrick       }
168e5dd7070Spatrick       break;
169e5dd7070Spatrick     }
170e5dd7070Spatrick   }
171e5dd7070Spatrick 
172e5dd7070Spatrick   for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
173e5dd7070Spatrick     FileEdit &FA = I->second;
174e5dd7070Spatrick     FileOffset B = I->first;
175e5dd7070Spatrick     FileOffset E = B.getWithOffset(FA.RemoveLen);
176e5dd7070Spatrick 
177e5dd7070Spatrick     if (BeginOffs < B) {
178e5dd7070Spatrick       bool Invalid = false;
179e5dd7070Spatrick       StringRef text = getSourceText(BeginOffs, B, Invalid);
180e5dd7070Spatrick       if (Invalid)
181e5dd7070Spatrick         return false;
182e5dd7070Spatrick       StrVec += text;
183e5dd7070Spatrick     }
184e5dd7070Spatrick     StrVec += FA.Text;
185e5dd7070Spatrick     BeginOffs = E;
186e5dd7070Spatrick   }
187e5dd7070Spatrick 
188e5dd7070Spatrick   if (BeginOffs < EndOffs) {
189e5dd7070Spatrick     bool Invalid = false;
190e5dd7070Spatrick     StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
191e5dd7070Spatrick     if (Invalid)
192e5dd7070Spatrick       return false;
193e5dd7070Spatrick     StrVec += text;
194e5dd7070Spatrick   }
195e5dd7070Spatrick 
196e5dd7070Spatrick   return commitInsert(OrigLoc, Offs, StrVec, beforePreviousInsertions);
197e5dd7070Spatrick }
198e5dd7070Spatrick 
commitRemove(SourceLocation OrigLoc,FileOffset BeginOffs,unsigned Len)199e5dd7070Spatrick void EditedSource::commitRemove(SourceLocation OrigLoc,
200e5dd7070Spatrick                                 FileOffset BeginOffs, unsigned Len) {
201e5dd7070Spatrick   if (Len == 0)
202e5dd7070Spatrick     return;
203e5dd7070Spatrick 
204e5dd7070Spatrick   FileOffset EndOffs = BeginOffs.getWithOffset(Len);
205e5dd7070Spatrick   FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
206e5dd7070Spatrick   if (I != FileEdits.begin())
207e5dd7070Spatrick     --I;
208e5dd7070Spatrick 
209e5dd7070Spatrick   for (; I != FileEdits.end(); ++I) {
210e5dd7070Spatrick     FileEdit &FA = I->second;
211e5dd7070Spatrick     FileOffset B = I->first;
212e5dd7070Spatrick     FileOffset E = B.getWithOffset(FA.RemoveLen);
213e5dd7070Spatrick 
214e5dd7070Spatrick     if (BeginOffs < E)
215e5dd7070Spatrick       break;
216e5dd7070Spatrick   }
217e5dd7070Spatrick 
218e5dd7070Spatrick   FileOffset TopBegin, TopEnd;
219e5dd7070Spatrick   FileEdit *TopFA = nullptr;
220e5dd7070Spatrick 
221e5dd7070Spatrick   if (I == FileEdits.end()) {
222e5dd7070Spatrick     FileEditsTy::iterator
223e5dd7070Spatrick       NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
224e5dd7070Spatrick     NewI->second.RemoveLen = Len;
225e5dd7070Spatrick     return;
226e5dd7070Spatrick   }
227e5dd7070Spatrick 
228e5dd7070Spatrick   FileEdit &FA = I->second;
229e5dd7070Spatrick   FileOffset B = I->first;
230e5dd7070Spatrick   FileOffset E = B.getWithOffset(FA.RemoveLen);
231e5dd7070Spatrick   if (BeginOffs < B) {
232e5dd7070Spatrick     FileEditsTy::iterator
233e5dd7070Spatrick       NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
234e5dd7070Spatrick     TopBegin = BeginOffs;
235e5dd7070Spatrick     TopEnd = EndOffs;
236e5dd7070Spatrick     TopFA = &NewI->second;
237e5dd7070Spatrick     TopFA->RemoveLen = Len;
238e5dd7070Spatrick   } else {
239e5dd7070Spatrick     TopBegin = B;
240e5dd7070Spatrick     TopEnd = E;
241e5dd7070Spatrick     TopFA = &I->second;
242e5dd7070Spatrick     if (TopEnd >= EndOffs)
243e5dd7070Spatrick       return;
244e5dd7070Spatrick     unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
245e5dd7070Spatrick     TopEnd = EndOffs;
246e5dd7070Spatrick     TopFA->RemoveLen += diff;
247e5dd7070Spatrick     if (B == BeginOffs)
248e5dd7070Spatrick       TopFA->Text = StringRef();
249e5dd7070Spatrick     ++I;
250e5dd7070Spatrick   }
251e5dd7070Spatrick 
252e5dd7070Spatrick   while (I != FileEdits.end()) {
253e5dd7070Spatrick     FileEdit &FA = I->second;
254e5dd7070Spatrick     FileOffset B = I->first;
255e5dd7070Spatrick     FileOffset E = B.getWithOffset(FA.RemoveLen);
256e5dd7070Spatrick 
257e5dd7070Spatrick     if (B >= TopEnd)
258e5dd7070Spatrick       break;
259e5dd7070Spatrick 
260e5dd7070Spatrick     if (E <= TopEnd) {
261e5dd7070Spatrick       FileEdits.erase(I++);
262e5dd7070Spatrick       continue;
263e5dd7070Spatrick     }
264e5dd7070Spatrick 
265e5dd7070Spatrick     if (B < TopEnd) {
266e5dd7070Spatrick       unsigned diff = E.getOffset() - TopEnd.getOffset();
267e5dd7070Spatrick       TopEnd = E;
268e5dd7070Spatrick       TopFA->RemoveLen += diff;
269e5dd7070Spatrick       FileEdits.erase(I);
270e5dd7070Spatrick     }
271e5dd7070Spatrick 
272e5dd7070Spatrick     break;
273e5dd7070Spatrick   }
274e5dd7070Spatrick }
275e5dd7070Spatrick 
commit(const Commit & commit)276e5dd7070Spatrick bool EditedSource::commit(const Commit &commit) {
277e5dd7070Spatrick   if (!commit.isCommitable())
278e5dd7070Spatrick     return false;
279e5dd7070Spatrick 
280e5dd7070Spatrick   struct CommitRAII {
281e5dd7070Spatrick     EditedSource &Editor;
282e5dd7070Spatrick 
283e5dd7070Spatrick     CommitRAII(EditedSource &Editor) : Editor(Editor) {
284e5dd7070Spatrick       Editor.startingCommit();
285e5dd7070Spatrick     }
286e5dd7070Spatrick 
287e5dd7070Spatrick     ~CommitRAII() {
288e5dd7070Spatrick       Editor.finishedCommit();
289e5dd7070Spatrick     }
290e5dd7070Spatrick   } CommitRAII(*this);
291e5dd7070Spatrick 
292e5dd7070Spatrick   for (edit::Commit::edit_iterator
293e5dd7070Spatrick          I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
294e5dd7070Spatrick     const edit::Commit::Edit &edit = *I;
295e5dd7070Spatrick     switch (edit.Kind) {
296e5dd7070Spatrick     case edit::Commit::Act_Insert:
297e5dd7070Spatrick       commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
298e5dd7070Spatrick       break;
299e5dd7070Spatrick     case edit::Commit::Act_InsertFromRange:
300e5dd7070Spatrick       commitInsertFromRange(edit.OrigLoc, edit.Offset,
301e5dd7070Spatrick                             edit.InsertFromRangeOffs, edit.Length,
302e5dd7070Spatrick                             edit.BeforePrev);
303e5dd7070Spatrick       break;
304e5dd7070Spatrick     case edit::Commit::Act_Remove:
305e5dd7070Spatrick       commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
306e5dd7070Spatrick       break;
307e5dd7070Spatrick     }
308e5dd7070Spatrick   }
309e5dd7070Spatrick 
310e5dd7070Spatrick   return true;
311e5dd7070Spatrick }
312e5dd7070Spatrick 
313e5dd7070Spatrick // Returns true if it is ok to make the two given characters adjacent.
canBeJoined(char left,char right,const LangOptions & LangOpts)314e5dd7070Spatrick static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
315e5dd7070Spatrick   // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
316e5dd7070Spatrick   // making two '<' adjacent.
317*12c85518Srobert   return !(Lexer::isAsciiIdentifierContinueChar(left, LangOpts) &&
318*12c85518Srobert            Lexer::isAsciiIdentifierContinueChar(right, LangOpts));
319e5dd7070Spatrick }
320e5dd7070Spatrick 
321e5dd7070Spatrick /// Returns true if it is ok to eliminate the trailing whitespace between
322e5dd7070Spatrick /// the given characters.
canRemoveWhitespace(char left,char beforeWSpace,char right,const LangOptions & LangOpts)323e5dd7070Spatrick static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
324e5dd7070Spatrick                                 const LangOptions &LangOpts) {
325e5dd7070Spatrick   if (!canBeJoined(left, right, LangOpts))
326e5dd7070Spatrick     return false;
327e5dd7070Spatrick   if (isWhitespace(left) || isWhitespace(right))
328e5dd7070Spatrick     return true;
329e5dd7070Spatrick   if (canBeJoined(beforeWSpace, right, LangOpts))
330e5dd7070Spatrick     return false; // the whitespace was intentional, keep it.
331e5dd7070Spatrick   return true;
332e5dd7070Spatrick }
333e5dd7070Spatrick 
334e5dd7070Spatrick /// Check the range that we are going to remove and:
335e5dd7070Spatrick /// -Remove any trailing whitespace if possible.
336e5dd7070Spatrick /// -Insert a space if removing the range is going to mess up the source tokens.
adjustRemoval(const SourceManager & SM,const LangOptions & LangOpts,SourceLocation Loc,FileOffset offs,unsigned & len,StringRef & text)337e5dd7070Spatrick static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
338e5dd7070Spatrick                           SourceLocation Loc, FileOffset offs,
339e5dd7070Spatrick                           unsigned &len, StringRef &text) {
340e5dd7070Spatrick   assert(len && text.empty());
341e5dd7070Spatrick   SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
342e5dd7070Spatrick   if (BeginTokLoc != Loc)
343e5dd7070Spatrick     return; // the range is not at the beginning of a token, keep the range.
344e5dd7070Spatrick 
345e5dd7070Spatrick   bool Invalid = false;
346e5dd7070Spatrick   StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
347e5dd7070Spatrick   if (Invalid)
348e5dd7070Spatrick     return;
349e5dd7070Spatrick 
350e5dd7070Spatrick   unsigned begin = offs.getOffset();
351e5dd7070Spatrick   unsigned end = begin + len;
352e5dd7070Spatrick 
353e5dd7070Spatrick   // Do not try to extend the removal if we're at the end of the buffer already.
354e5dd7070Spatrick   if (end == buffer.size())
355e5dd7070Spatrick     return;
356e5dd7070Spatrick 
357e5dd7070Spatrick   assert(begin < buffer.size() && end < buffer.size() && "Invalid range!");
358e5dd7070Spatrick 
359e5dd7070Spatrick   // FIXME: Remove newline.
360e5dd7070Spatrick 
361e5dd7070Spatrick   if (begin == 0) {
362e5dd7070Spatrick     if (buffer[end] == ' ')
363e5dd7070Spatrick       ++len;
364e5dd7070Spatrick     return;
365e5dd7070Spatrick   }
366e5dd7070Spatrick 
367e5dd7070Spatrick   if (buffer[end] == ' ') {
368e5dd7070Spatrick     assert((end + 1 != buffer.size() || buffer.data()[end + 1] == 0) &&
369e5dd7070Spatrick            "buffer not zero-terminated!");
370e5dd7070Spatrick     if (canRemoveWhitespace(/*left=*/buffer[begin-1],
371e5dd7070Spatrick                             /*beforeWSpace=*/buffer[end-1],
372e5dd7070Spatrick                             /*right=*/buffer.data()[end + 1], // zero-terminated
373e5dd7070Spatrick                             LangOpts))
374e5dd7070Spatrick       ++len;
375e5dd7070Spatrick     return;
376e5dd7070Spatrick   }
377e5dd7070Spatrick 
378e5dd7070Spatrick   if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
379e5dd7070Spatrick     text = " ";
380e5dd7070Spatrick }
381e5dd7070Spatrick 
applyRewrite(EditsReceiver & receiver,StringRef text,FileOffset offs,unsigned len,const SourceManager & SM,const LangOptions & LangOpts,bool shouldAdjustRemovals)382e5dd7070Spatrick static void applyRewrite(EditsReceiver &receiver,
383e5dd7070Spatrick                          StringRef text, FileOffset offs, unsigned len,
384e5dd7070Spatrick                          const SourceManager &SM, const LangOptions &LangOpts,
385e5dd7070Spatrick                          bool shouldAdjustRemovals) {
386e5dd7070Spatrick   assert(offs.getFID().isValid());
387e5dd7070Spatrick   SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
388e5dd7070Spatrick   Loc = Loc.getLocWithOffset(offs.getOffset());
389e5dd7070Spatrick   assert(Loc.isFileID());
390e5dd7070Spatrick 
391e5dd7070Spatrick   if (text.empty() && shouldAdjustRemovals)
392e5dd7070Spatrick     adjustRemoval(SM, LangOpts, Loc, offs, len, text);
393e5dd7070Spatrick 
394e5dd7070Spatrick   CharSourceRange range = CharSourceRange::getCharRange(Loc,
395e5dd7070Spatrick                                                      Loc.getLocWithOffset(len));
396e5dd7070Spatrick 
397e5dd7070Spatrick   if (text.empty()) {
398e5dd7070Spatrick     assert(len);
399e5dd7070Spatrick     receiver.remove(range);
400e5dd7070Spatrick     return;
401e5dd7070Spatrick   }
402e5dd7070Spatrick 
403e5dd7070Spatrick   if (len)
404e5dd7070Spatrick     receiver.replace(range, text);
405e5dd7070Spatrick   else
406e5dd7070Spatrick     receiver.insert(Loc, text);
407e5dd7070Spatrick }
408e5dd7070Spatrick 
applyRewrites(EditsReceiver & receiver,bool shouldAdjustRemovals)409e5dd7070Spatrick void EditedSource::applyRewrites(EditsReceiver &receiver,
410e5dd7070Spatrick                                  bool shouldAdjustRemovals) {
411e5dd7070Spatrick   SmallString<128> StrVec;
412e5dd7070Spatrick   FileOffset CurOffs, CurEnd;
413e5dd7070Spatrick   unsigned CurLen;
414e5dd7070Spatrick 
415e5dd7070Spatrick   if (FileEdits.empty())
416e5dd7070Spatrick     return;
417e5dd7070Spatrick 
418e5dd7070Spatrick   FileEditsTy::iterator I = FileEdits.begin();
419e5dd7070Spatrick   CurOffs = I->first;
420e5dd7070Spatrick   StrVec = I->second.Text;
421e5dd7070Spatrick   CurLen = I->second.RemoveLen;
422e5dd7070Spatrick   CurEnd = CurOffs.getWithOffset(CurLen);
423e5dd7070Spatrick   ++I;
424e5dd7070Spatrick 
425e5dd7070Spatrick   for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
426e5dd7070Spatrick     FileOffset offs = I->first;
427e5dd7070Spatrick     FileEdit act = I->second;
428e5dd7070Spatrick     assert(offs >= CurEnd);
429e5dd7070Spatrick 
430e5dd7070Spatrick     if (offs == CurEnd) {
431e5dd7070Spatrick       StrVec += act.Text;
432e5dd7070Spatrick       CurLen += act.RemoveLen;
433e5dd7070Spatrick       CurEnd.getWithOffset(act.RemoveLen);
434e5dd7070Spatrick       continue;
435e5dd7070Spatrick     }
436e5dd7070Spatrick 
437e5dd7070Spatrick     applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,
438e5dd7070Spatrick                  shouldAdjustRemovals);
439e5dd7070Spatrick     CurOffs = offs;
440e5dd7070Spatrick     StrVec = act.Text;
441e5dd7070Spatrick     CurLen = act.RemoveLen;
442e5dd7070Spatrick     CurEnd = CurOffs.getWithOffset(CurLen);
443e5dd7070Spatrick   }
444e5dd7070Spatrick 
445e5dd7070Spatrick   applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,
446e5dd7070Spatrick                shouldAdjustRemovals);
447e5dd7070Spatrick }
448e5dd7070Spatrick 
clearRewrites()449e5dd7070Spatrick void EditedSource::clearRewrites() {
450e5dd7070Spatrick   FileEdits.clear();
451e5dd7070Spatrick   StrAlloc.Reset();
452e5dd7070Spatrick }
453e5dd7070Spatrick 
getSourceText(FileOffset BeginOffs,FileOffset EndOffs,bool & Invalid)454e5dd7070Spatrick StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
455e5dd7070Spatrick                                       bool &Invalid) {
456e5dd7070Spatrick   assert(BeginOffs.getFID() == EndOffs.getFID());
457e5dd7070Spatrick   assert(BeginOffs <= EndOffs);
458e5dd7070Spatrick   SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
459e5dd7070Spatrick   BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
460e5dd7070Spatrick   assert(BLoc.isFileID());
461e5dd7070Spatrick   SourceLocation
462e5dd7070Spatrick     ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
463e5dd7070Spatrick   return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
464e5dd7070Spatrick                               SourceMgr, LangOpts, &Invalid);
465e5dd7070Spatrick }
466e5dd7070Spatrick 
467e5dd7070Spatrick EditedSource::FileEditsTy::iterator
getActionForOffset(FileOffset Offs)468e5dd7070Spatrick EditedSource::getActionForOffset(FileOffset Offs) {
469e5dd7070Spatrick   FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
470e5dd7070Spatrick   if (I == FileEdits.begin())
471e5dd7070Spatrick     return FileEdits.end();
472e5dd7070Spatrick   --I;
473e5dd7070Spatrick   FileEdit &FA = I->second;
474e5dd7070Spatrick   FileOffset B = I->first;
475e5dd7070Spatrick   FileOffset E = B.getWithOffset(FA.RemoveLen);
476e5dd7070Spatrick   if (Offs >= B && Offs < E)
477e5dd7070Spatrick     return I;
478e5dd7070Spatrick 
479e5dd7070Spatrick   return FileEdits.end();
480e5dd7070Spatrick }
481