EditedSource.cpp revision 6bcf27bb9a4b5c3f79cb44c0e4654a6d7619ad89
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
19using namespace clang;
20using namespace edit;
21
22void EditsReceiver::remove(CharSourceRange range) {
23  replace(range, StringRef());
24}
25
26StringRef EditedSource::copyString(const Twine &twine) {
27  SmallString<128> Data;
28  return copyString(twine.toStringRef(Data));
29}
30
31bool 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
53bool 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
83bool 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.str(), beforePreviousInsertions);
139}
140
141void 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
218bool 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.
244static 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.
253static 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.
267static 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  // FIXME: Remove newline.
284
285  if (begin == 0) {
286    if (buffer[end] == ' ')
287      ++len;
288    return;
289  }
290
291  if (buffer[end] == ' ') {
292    if (canRemoveWhitespace(/*left=*/buffer[begin-1],
293                            /*beforeWSpace=*/buffer[end-1],
294                            /*right=*/buffer[end+1],
295                            LangOpts))
296      ++len;
297    return;
298  }
299
300  if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
301    text = " ";
302}
303
304static void applyRewrite(EditsReceiver &receiver,
305                         StringRef text, FileOffset offs, unsigned len,
306                         const SourceManager &SM, const LangOptions &LangOpts) {
307  assert(!offs.getFID().isInvalid());
308  SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
309  Loc = Loc.getLocWithOffset(offs.getOffset());
310  assert(Loc.isFileID());
311
312  if (text.empty())
313    adjustRemoval(SM, LangOpts, Loc, offs, len, text);
314
315  CharSourceRange range = CharSourceRange::getCharRange(Loc,
316                                                     Loc.getLocWithOffset(len));
317
318  if (text.empty()) {
319    assert(len);
320    receiver.remove(range);
321    return;
322  }
323
324  if (len)
325    receiver.replace(range, text);
326  else
327    receiver.insert(Loc, text);
328}
329
330void EditedSource::applyRewrites(EditsReceiver &receiver) {
331  SmallString<128> StrVec;
332  FileOffset CurOffs, CurEnd;
333  unsigned CurLen;
334
335  if (FileEdits.empty())
336    return;
337
338  FileEditsTy::iterator I = FileEdits.begin();
339  CurOffs = I->first;
340  StrVec = I->second.Text;
341  CurLen = I->second.RemoveLen;
342  CurEnd = CurOffs.getWithOffset(CurLen);
343  ++I;
344
345  for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
346    FileOffset offs = I->first;
347    FileEdit act = I->second;
348    assert(offs >= CurEnd);
349
350    if (offs == CurEnd) {
351      StrVec += act.Text;
352      CurLen += act.RemoveLen;
353      CurEnd.getWithOffset(act.RemoveLen);
354      continue;
355    }
356
357    applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
358    CurOffs = offs;
359    StrVec = act.Text;
360    CurLen = act.RemoveLen;
361    CurEnd = CurOffs.getWithOffset(CurLen);
362  }
363
364  applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
365}
366
367void EditedSource::clearRewrites() {
368  FileEdits.clear();
369  StrAlloc.Reset();
370}
371
372StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
373                                      bool &Invalid) {
374  assert(BeginOffs.getFID() == EndOffs.getFID());
375  assert(BeginOffs <= EndOffs);
376  SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
377  BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
378  assert(BLoc.isFileID());
379  SourceLocation
380    ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
381  return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
382                              SourceMgr, LangOpts, &Invalid);
383}
384
385EditedSource::FileEditsTy::iterator
386EditedSource::getActionForOffset(FileOffset Offs) {
387  FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
388  if (I == FileEdits.begin())
389    return FileEdits.end();
390  --I;
391  FileEdit &FA = I->second;
392  FileOffset B = I->first;
393  FileOffset E = B.getWithOffset(FA.RemoveLen);
394  if (Offs >= B && Offs < E)
395    return I;
396
397  return FileEdits.end();
398}
399