EditedSource.cpp revision f50b8f0a8ea2a6537c112390bc4054eba390326b
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/SourceManager.h"
12#include "clang/Edit/Commit.h"
13#include "clang/Edit/EditsReceiver.h"
14#include "clang/Lex/Lexer.h"
15#include "llvm/ADT/SmallString.h"
16#include "llvm/ADT/Twine.h"
17#include <cctype>
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  llvm::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  Twine concat;
76  if (beforePreviousInsertions)
77    concat = Twine(text) + FA.Text;
78  else
79    concat = Twine(FA.Text) +  text;
80
81  FA.Text = copyString(concat);
82  return true;
83}
84
85bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
86                                   FileOffset Offs,
87                                   FileOffset InsertFromRangeOffs, unsigned Len,
88                                   bool beforePreviousInsertions) {
89  if (Len == 0)
90    return true;
91
92  llvm::SmallString<128> StrVec;
93  FileOffset BeginOffs = InsertFromRangeOffs;
94  FileOffset EndOffs = BeginOffs.getWithOffset(Len);
95  FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
96  if (I != FileEdits.begin())
97    --I;
98
99  for (; I != FileEdits.end(); ++I) {
100    FileEdit &FA = I->second;
101    FileOffset B = I->first;
102    FileOffset E = B.getWithOffset(FA.RemoveLen);
103
104    if (BeginOffs == B)
105      break;
106
107    if (BeginOffs < E) {
108      if (BeginOffs > B) {
109        BeginOffs = E;
110        ++I;
111      }
112      break;
113    }
114  }
115
116  for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
117    FileEdit &FA = I->second;
118    FileOffset B = I->first;
119    FileOffset E = B.getWithOffset(FA.RemoveLen);
120
121    if (BeginOffs < B) {
122      bool Invalid = false;
123      StringRef text = getSourceText(BeginOffs, B, Invalid);
124      if (Invalid)
125        return false;
126      StrVec += text;
127    }
128    StrVec += FA.Text;
129    BeginOffs = E;
130  }
131
132  if (BeginOffs < EndOffs) {
133    bool Invalid = false;
134    StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
135    if (Invalid)
136      return false;
137    StrVec += text;
138  }
139
140  return commitInsert(OrigLoc, Offs, StrVec.str(), beforePreviousInsertions);
141}
142
143void EditedSource::commitRemove(SourceLocation OrigLoc,
144                                FileOffset BeginOffs, unsigned Len) {
145  if (Len == 0)
146    return;
147
148  FileOffset EndOffs = BeginOffs.getWithOffset(Len);
149  FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
150  if (I != FileEdits.begin())
151    --I;
152
153  for (; I != FileEdits.end(); ++I) {
154    FileEdit &FA = I->second;
155    FileOffset B = I->first;
156    FileOffset E = B.getWithOffset(FA.RemoveLen);
157
158    if (BeginOffs < E)
159      break;
160  }
161
162  FileOffset TopBegin, TopEnd;
163  FileEdit *TopFA = 0;
164
165  if (I == FileEdits.end()) {
166    FileEditsTy::iterator
167      NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
168    NewI->second.RemoveLen = Len;
169    return;
170  }
171
172  FileEdit &FA = I->second;
173  FileOffset B = I->first;
174  FileOffset E = B.getWithOffset(FA.RemoveLen);
175  if (BeginOffs < B) {
176    FileEditsTy::iterator
177      NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
178    TopBegin = BeginOffs;
179    TopEnd = EndOffs;
180    TopFA = &NewI->second;
181    TopFA->RemoveLen = Len;
182  } else {
183    TopBegin = B;
184    TopEnd = E;
185    TopFA = &I->second;
186    if (TopEnd >= EndOffs)
187      return;
188    unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
189    TopEnd = EndOffs;
190    TopFA->RemoveLen += diff;
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
243static inline bool isIdentifierChar(char c, const LangOptions &LangOpts) {
244  return std::isalnum(c) || c == '_' || (c == '$' && LangOpts.DollarIdents);
245}
246
247// \brief Returns true if it is ok to make the two given characters adjacent.
248static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
249  // FIXME: Should use the Lexer to make sure we don't allow stuff like
250  // making two '<' adjacent.
251  return !(isIdentifierChar(left, LangOpts) &&
252           isIdentifierChar(right, LangOpts));
253}
254
255/// \brief Returns true if it is ok to eliminate the trailing whitespace between
256/// the given characters.
257static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
258                                const LangOptions &LangOpts) {
259  if (!canBeJoined(left, right, LangOpts))
260    return false;
261  if (std::isspace(left) || std::isspace(right))
262    return true;
263  if (canBeJoined(beforeWSpace, right, LangOpts))
264    return false; // the whitespace was intentional, keep it.
265  return true;
266}
267
268/// \brief Check the range that we are going to remove and:
269/// -Remove any trailing whitespace if possible.
270/// -Insert a space if removing the range is going to mess up the source tokens.
271static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
272                          SourceLocation Loc, FileOffset offs,
273                          unsigned &len, StringRef &text) {
274  assert(len && text.empty());
275  SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
276  if (BeginTokLoc != Loc)
277    return; // the range is not at the beginning of a token, keep the range.
278
279  bool Invalid = false;
280  StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
281  if (Invalid)
282    return;
283
284  unsigned begin = offs.getOffset();
285  unsigned end = begin + len;
286
287  // FIXME: Remove newline.
288
289  if (begin == 0) {
290    if (buffer[end] == ' ')
291      ++len;
292    return;
293  }
294
295  if (buffer[end] == ' ') {
296    if (canRemoveWhitespace(/*left=*/buffer[begin-1],
297                            /*beforeWSpace=*/buffer[end-1],
298                            /*right=*/buffer[end+1],
299                            LangOpts))
300      ++len;
301    return;
302  }
303
304  if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
305    text = " ";
306}
307
308static void applyRewrite(EditsReceiver &receiver,
309                         StringRef text, FileOffset offs, unsigned len,
310                         const SourceManager &SM, const LangOptions &LangOpts) {
311  assert(!offs.getFID().isInvalid());
312  SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
313  Loc = Loc.getLocWithOffset(offs.getOffset());
314  assert(Loc.isFileID());
315
316  if (text.empty())
317    adjustRemoval(SM, LangOpts, Loc, offs, len, text);
318
319  CharSourceRange range = CharSourceRange::getCharRange(Loc,
320                                                     Loc.getLocWithOffset(len));
321
322  if (text.empty()) {
323    assert(len);
324    receiver.remove(range);
325    return;
326  }
327
328  if (len)
329    receiver.replace(range, text);
330  else
331    receiver.insert(Loc, text);
332}
333
334void EditedSource::applyRewrites(EditsReceiver &receiver) {
335  llvm::SmallString<128> StrVec;
336  FileOffset CurOffs, CurEnd;
337  unsigned CurLen;
338
339  if (FileEdits.empty())
340    return;
341
342  FileEditsTy::iterator I = FileEdits.begin();
343  CurOffs = I->first;
344  StrVec = I->second.Text;
345  CurLen = I->second.RemoveLen;
346  CurEnd = CurOffs.getWithOffset(CurLen);
347  ++I;
348
349  for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
350    FileOffset offs = I->first;
351    FileEdit act = I->second;
352    assert(offs >= CurEnd);
353
354    if (offs == CurEnd) {
355      StrVec += act.Text;
356      CurLen += act.RemoveLen;
357      CurEnd.getWithOffset(act.RemoveLen);
358      continue;
359    }
360
361    applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
362    CurOffs = offs;
363    StrVec = act.Text;
364    CurLen = act.RemoveLen;
365    CurEnd = CurOffs.getWithOffset(CurLen);
366  }
367
368  applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
369}
370
371void EditedSource::clearRewrites() {
372  FileEdits.clear();
373  StrAlloc.Reset();
374}
375
376StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
377                                      bool &Invalid) {
378  assert(BeginOffs.getFID() == EndOffs.getFID());
379  assert(BeginOffs <= EndOffs);
380  SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
381  BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
382  assert(BLoc.isFileID());
383  SourceLocation
384    ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
385  return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
386                              SourceMgr, LangOpts, &Invalid);
387}
388
389EditedSource::FileEditsTy::iterator
390EditedSource::getActionForOffset(FileOffset Offs) {
391  FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
392  if (I == FileEdits.begin())
393    return FileEdits.end();
394  --I;
395  FileEdit &FA = I->second;
396  FileOffset B = I->first;
397  FileOffset E = B.getWithOffset(FA.RemoveLen);
398  if (Offs >= B && Offs < E)
399    return I;
400
401  return FileEdits.end();
402}
403