1 //===----- EditedSource.cpp - Collection of source edits ------------------===//
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/Edit/EditedSource.h"
11 #include "clang/Basic/CharInfo.h"
12 #include "clang/Basic/SourceManager.h"
13 #include "clang/Edit/Commit.h"
14 #include "clang/Edit/EditsReceiver.h"
15 #include "clang/Lex/Lexer.h"
16 #include "llvm/ADT/SmallString.h"
17 #include "llvm/ADT/Twine.h"
18 
19 using namespace clang;
20 using namespace edit;
21 
remove(CharSourceRange range)22 void EditsReceiver::remove(CharSourceRange range) {
23   replace(range, StringRef());
24 }
25 
copyString(const Twine & twine)26 StringRef EditedSource::copyString(const Twine &twine) {
27   SmallString<128> Data;
28   return copyString(twine.toStringRef(Data));
29 }
30 
canInsertInOffset(SourceLocation OrigLoc,FileOffset Offs)31 bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
32   FileEditsTy::iterator FA = getActionForOffset(Offs);
33   if (FA != FileEdits.end()) {
34     if (FA->first != Offs)
35       return false; // position has been removed.
36   }
37 
38   if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
39     SourceLocation
40       DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
41     SourceLocation
42       ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
43     llvm::DenseMap<unsigned, SourceLocation>::iterator
44       I = ExpansionToArgMap.find(ExpLoc.getRawEncoding());
45     if (I != ExpansionToArgMap.end() && I->second != DefArgLoc)
46       return false; // Trying to write in a macro argument input that has
47                  // already been written for another argument of the same macro.
48   }
49 
50   return true;
51 }
52 
commitInsert(SourceLocation OrigLoc,FileOffset Offs,StringRef text,bool beforePreviousInsertions)53 bool EditedSource::commitInsert(SourceLocation OrigLoc,
54                                 FileOffset Offs, StringRef text,
55                                 bool beforePreviousInsertions) {
56   if (!canInsertInOffset(OrigLoc, Offs))
57     return false;
58   if (text.empty())
59     return true;
60 
61   if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
62     SourceLocation
63       DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
64     SourceLocation
65       ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
66     ExpansionToArgMap[ExpLoc.getRawEncoding()] = DefArgLoc;
67   }
68 
69   FileEdit &FA = FileEdits[Offs];
70   if (FA.Text.empty()) {
71     FA.Text = copyString(text);
72     return true;
73   }
74 
75   if (beforePreviousInsertions)
76     FA.Text = copyString(Twine(text) + FA.Text);
77   else
78     FA.Text = copyString(Twine(FA.Text) + text);
79 
80   return true;
81 }
82 
commitInsertFromRange(SourceLocation OrigLoc,FileOffset Offs,FileOffset InsertFromRangeOffs,unsigned Len,bool beforePreviousInsertions)83 bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
84                                    FileOffset Offs,
85                                    FileOffset InsertFromRangeOffs, unsigned Len,
86                                    bool beforePreviousInsertions) {
87   if (Len == 0)
88     return true;
89 
90   SmallString<128> StrVec;
91   FileOffset BeginOffs = InsertFromRangeOffs;
92   FileOffset EndOffs = BeginOffs.getWithOffset(Len);
93   FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
94   if (I != FileEdits.begin())
95     --I;
96 
97   for (; I != FileEdits.end(); ++I) {
98     FileEdit &FA = I->second;
99     FileOffset B = I->first;
100     FileOffset E = B.getWithOffset(FA.RemoveLen);
101 
102     if (BeginOffs == B)
103       break;
104 
105     if (BeginOffs < E) {
106       if (BeginOffs > B) {
107         BeginOffs = E;
108         ++I;
109       }
110       break;
111     }
112   }
113 
114   for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
115     FileEdit &FA = I->second;
116     FileOffset B = I->first;
117     FileOffset E = B.getWithOffset(FA.RemoveLen);
118 
119     if (BeginOffs < B) {
120       bool Invalid = false;
121       StringRef text = getSourceText(BeginOffs, B, Invalid);
122       if (Invalid)
123         return false;
124       StrVec += text;
125     }
126     StrVec += FA.Text;
127     BeginOffs = E;
128   }
129 
130   if (BeginOffs < EndOffs) {
131     bool Invalid = false;
132     StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
133     if (Invalid)
134       return false;
135     StrVec += text;
136   }
137 
138   return commitInsert(OrigLoc, Offs, StrVec, beforePreviousInsertions);
139 }
140 
commitRemove(SourceLocation OrigLoc,FileOffset BeginOffs,unsigned Len)141 void EditedSource::commitRemove(SourceLocation OrigLoc,
142                                 FileOffset BeginOffs, unsigned Len) {
143   if (Len == 0)
144     return;
145 
146   FileOffset EndOffs = BeginOffs.getWithOffset(Len);
147   FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
148   if (I != FileEdits.begin())
149     --I;
150 
151   for (; I != FileEdits.end(); ++I) {
152     FileEdit &FA = I->second;
153     FileOffset B = I->first;
154     FileOffset E = B.getWithOffset(FA.RemoveLen);
155 
156     if (BeginOffs < E)
157       break;
158   }
159 
160   FileOffset TopBegin, TopEnd;
161   FileEdit *TopFA = nullptr;
162 
163   if (I == FileEdits.end()) {
164     FileEditsTy::iterator
165       NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
166     NewI->second.RemoveLen = Len;
167     return;
168   }
169 
170   FileEdit &FA = I->second;
171   FileOffset B = I->first;
172   FileOffset E = B.getWithOffset(FA.RemoveLen);
173   if (BeginOffs < B) {
174     FileEditsTy::iterator
175       NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
176     TopBegin = BeginOffs;
177     TopEnd = EndOffs;
178     TopFA = &NewI->second;
179     TopFA->RemoveLen = Len;
180   } else {
181     TopBegin = B;
182     TopEnd = E;
183     TopFA = &I->second;
184     if (TopEnd >= EndOffs)
185       return;
186     unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
187     TopEnd = EndOffs;
188     TopFA->RemoveLen += diff;
189     if (B == BeginOffs)
190       TopFA->Text = StringRef();
191     ++I;
192   }
193 
194   while (I != FileEdits.end()) {
195     FileEdit &FA = I->second;
196     FileOffset B = I->first;
197     FileOffset E = B.getWithOffset(FA.RemoveLen);
198 
199     if (B >= TopEnd)
200       break;
201 
202     if (E <= TopEnd) {
203       FileEdits.erase(I++);
204       continue;
205     }
206 
207     if (B < TopEnd) {
208       unsigned diff = E.getOffset() - TopEnd.getOffset();
209       TopEnd = E;
210       TopFA->RemoveLen += diff;
211       FileEdits.erase(I);
212     }
213 
214     break;
215   }
216 }
217 
commit(const Commit & commit)218 bool EditedSource::commit(const Commit &commit) {
219   if (!commit.isCommitable())
220     return false;
221 
222   for (edit::Commit::edit_iterator
223          I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
224     const edit::Commit::Edit &edit = *I;
225     switch (edit.Kind) {
226     case edit::Commit::Act_Insert:
227       commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
228       break;
229     case edit::Commit::Act_InsertFromRange:
230       commitInsertFromRange(edit.OrigLoc, edit.Offset,
231                             edit.InsertFromRangeOffs, edit.Length,
232                             edit.BeforePrev);
233       break;
234     case edit::Commit::Act_Remove:
235       commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
236       break;
237     }
238   }
239 
240   return true;
241 }
242 
243 // \brief Returns true if it is ok to make the two given characters adjacent.
canBeJoined(char left,char right,const LangOptions & LangOpts)244 static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
245   // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
246   // making two '<' adjacent.
247   return !(Lexer::isIdentifierBodyChar(left, LangOpts) &&
248            Lexer::isIdentifierBodyChar(right, LangOpts));
249 }
250 
251 /// \brief Returns true if it is ok to eliminate the trailing whitespace between
252 /// the given characters.
canRemoveWhitespace(char left,char beforeWSpace,char right,const LangOptions & LangOpts)253 static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
254                                 const LangOptions &LangOpts) {
255   if (!canBeJoined(left, right, LangOpts))
256     return false;
257   if (isWhitespace(left) || isWhitespace(right))
258     return true;
259   if (canBeJoined(beforeWSpace, right, LangOpts))
260     return false; // the whitespace was intentional, keep it.
261   return true;
262 }
263 
264 /// \brief Check the range that we are going to remove and:
265 /// -Remove any trailing whitespace if possible.
266 /// -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)267 static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
268                           SourceLocation Loc, FileOffset offs,
269                           unsigned &len, StringRef &text) {
270   assert(len && text.empty());
271   SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
272   if (BeginTokLoc != Loc)
273     return; // the range is not at the beginning of a token, keep the range.
274 
275   bool Invalid = false;
276   StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
277   if (Invalid)
278     return;
279 
280   unsigned begin = offs.getOffset();
281   unsigned end = begin + len;
282 
283   // Do not try to extend the removal if we're at the end of the buffer already.
284   if (end == buffer.size())
285     return;
286 
287   assert(begin < buffer.size() && end < buffer.size() && "Invalid range!");
288 
289   // FIXME: Remove newline.
290 
291   if (begin == 0) {
292     if (buffer[end] == ' ')
293       ++len;
294     return;
295   }
296 
297   if (buffer[end] == ' ') {
298     assert((end + 1 != buffer.size() || buffer.data()[end + 1] == 0) &&
299            "buffer not zero-terminated!");
300     if (canRemoveWhitespace(/*left=*/buffer[begin-1],
301                             /*beforeWSpace=*/buffer[end-1],
302                             /*right=*/buffer.data()[end + 1], // zero-terminated
303                             LangOpts))
304       ++len;
305     return;
306   }
307 
308   if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
309     text = " ";
310 }
311 
applyRewrite(EditsReceiver & receiver,StringRef text,FileOffset offs,unsigned len,const SourceManager & SM,const LangOptions & LangOpts)312 static void applyRewrite(EditsReceiver &receiver,
313                          StringRef text, FileOffset offs, unsigned len,
314                          const SourceManager &SM, const LangOptions &LangOpts) {
315   assert(!offs.getFID().isInvalid());
316   SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
317   Loc = Loc.getLocWithOffset(offs.getOffset());
318   assert(Loc.isFileID());
319 
320   if (text.empty())
321     adjustRemoval(SM, LangOpts, Loc, offs, len, text);
322 
323   CharSourceRange range = CharSourceRange::getCharRange(Loc,
324                                                      Loc.getLocWithOffset(len));
325 
326   if (text.empty()) {
327     assert(len);
328     receiver.remove(range);
329     return;
330   }
331 
332   if (len)
333     receiver.replace(range, text);
334   else
335     receiver.insert(Loc, text);
336 }
337 
applyRewrites(EditsReceiver & receiver)338 void EditedSource::applyRewrites(EditsReceiver &receiver) {
339   SmallString<128> StrVec;
340   FileOffset CurOffs, CurEnd;
341   unsigned CurLen;
342 
343   if (FileEdits.empty())
344     return;
345 
346   FileEditsTy::iterator I = FileEdits.begin();
347   CurOffs = I->first;
348   StrVec = I->second.Text;
349   CurLen = I->second.RemoveLen;
350   CurEnd = CurOffs.getWithOffset(CurLen);
351   ++I;
352 
353   for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
354     FileOffset offs = I->first;
355     FileEdit act = I->second;
356     assert(offs >= CurEnd);
357 
358     if (offs == CurEnd) {
359       StrVec += act.Text;
360       CurLen += act.RemoveLen;
361       CurEnd.getWithOffset(act.RemoveLen);
362       continue;
363     }
364 
365     applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts);
366     CurOffs = offs;
367     StrVec = act.Text;
368     CurLen = act.RemoveLen;
369     CurEnd = CurOffs.getWithOffset(CurLen);
370   }
371 
372   applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts);
373 }
374 
clearRewrites()375 void EditedSource::clearRewrites() {
376   FileEdits.clear();
377   StrAlloc.Reset();
378 }
379 
getSourceText(FileOffset BeginOffs,FileOffset EndOffs,bool & Invalid)380 StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
381                                       bool &Invalid) {
382   assert(BeginOffs.getFID() == EndOffs.getFID());
383   assert(BeginOffs <= EndOffs);
384   SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
385   BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
386   assert(BLoc.isFileID());
387   SourceLocation
388     ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
389   return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
390                               SourceMgr, LangOpts, &Invalid);
391 }
392 
393 EditedSource::FileEditsTy::iterator
getActionForOffset(FileOffset Offs)394 EditedSource::getActionForOffset(FileOffset Offs) {
395   FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
396   if (I == FileEdits.begin())
397     return FileEdits.end();
398   --I;
399   FileEdit &FA = I->second;
400   FileOffset B = I->first;
401   FileOffset E = B.getWithOffset(FA.RemoveLen);
402   if (Offs >= B && Offs < E)
403     return I;
404 
405   return FileEdits.end();
406 }
407