1234287Sdim//===----- EditedSource.cpp - Collection of source edits ------------------===//
2234287Sdim//
3234287Sdim//                     The LLVM Compiler Infrastructure
4234287Sdim//
5234287Sdim// This file is distributed under the University of Illinois Open Source
6234287Sdim// License. See LICENSE.TXT for details.
7234287Sdim//
8234287Sdim//===----------------------------------------------------------------------===//
9234287Sdim
10234287Sdim#include "clang/Edit/EditedSource.h"
11252723Sdim#include "clang/Basic/CharInfo.h"
12252723Sdim#include "clang/Basic/SourceManager.h"
13234287Sdim#include "clang/Edit/Commit.h"
14234287Sdim#include "clang/Edit/EditsReceiver.h"
15234287Sdim#include "clang/Lex/Lexer.h"
16234287Sdim#include "llvm/ADT/SmallString.h"
17234287Sdim#include "llvm/ADT/Twine.h"
18234287Sdim
19234287Sdimusing namespace clang;
20234287Sdimusing namespace edit;
21234287Sdim
22234287Sdimvoid EditsReceiver::remove(CharSourceRange range) {
23234287Sdim  replace(range, StringRef());
24234287Sdim}
25234287Sdim
26234287SdimStringRef EditedSource::copyString(const Twine &twine) {
27252723Sdim  SmallString<128> Data;
28234287Sdim  return copyString(twine.toStringRef(Data));
29234287Sdim}
30234287Sdim
31234287Sdimbool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
32234287Sdim  FileEditsTy::iterator FA = getActionForOffset(Offs);
33234287Sdim  if (FA != FileEdits.end()) {
34234287Sdim    if (FA->first != Offs)
35234287Sdim      return false; // position has been removed.
36234287Sdim  }
37234287Sdim
38234287Sdim  if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
39234287Sdim    SourceLocation
40234287Sdim      DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
41234287Sdim    SourceLocation
42234287Sdim      ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
43234287Sdim    llvm::DenseMap<unsigned, SourceLocation>::iterator
44234287Sdim      I = ExpansionToArgMap.find(ExpLoc.getRawEncoding());
45234287Sdim    if (I != ExpansionToArgMap.end() && I->second != DefArgLoc)
46234287Sdim      return false; // Trying to write in a macro argument input that has
47234287Sdim                 // already been written for another argument of the same macro.
48234287Sdim  }
49234287Sdim
50234287Sdim  return true;
51234287Sdim}
52234287Sdim
53234287Sdimbool EditedSource::commitInsert(SourceLocation OrigLoc,
54234287Sdim                                FileOffset Offs, StringRef text,
55234287Sdim                                bool beforePreviousInsertions) {
56234287Sdim  if (!canInsertInOffset(OrigLoc, Offs))
57234287Sdim    return false;
58234287Sdim  if (text.empty())
59234287Sdim    return true;
60234287Sdim
61234287Sdim  if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
62234287Sdim    SourceLocation
63234287Sdim      DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
64234287Sdim    SourceLocation
65234287Sdim      ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
66234287Sdim    ExpansionToArgMap[ExpLoc.getRawEncoding()] = DefArgLoc;
67234287Sdim  }
68234287Sdim
69234287Sdim  FileEdit &FA = FileEdits[Offs];
70234287Sdim  if (FA.Text.empty()) {
71234287Sdim    FA.Text = copyString(text);
72234287Sdim    return true;
73234287Sdim  }
74234287Sdim
75234287Sdim  Twine concat;
76234287Sdim  if (beforePreviousInsertions)
77234287Sdim    concat = Twine(text) + FA.Text;
78234287Sdim  else
79234287Sdim    concat = Twine(FA.Text) +  text;
80234287Sdim
81234287Sdim  FA.Text = copyString(concat);
82234287Sdim  return true;
83234287Sdim}
84234287Sdim
85234287Sdimbool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
86234287Sdim                                   FileOffset Offs,
87234287Sdim                                   FileOffset InsertFromRangeOffs, unsigned Len,
88234287Sdim                                   bool beforePreviousInsertions) {
89234287Sdim  if (Len == 0)
90234287Sdim    return true;
91234287Sdim
92252723Sdim  SmallString<128> StrVec;
93234287Sdim  FileOffset BeginOffs = InsertFromRangeOffs;
94234287Sdim  FileOffset EndOffs = BeginOffs.getWithOffset(Len);
95234287Sdim  FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
96234287Sdim  if (I != FileEdits.begin())
97234287Sdim    --I;
98234287Sdim
99234287Sdim  for (; I != FileEdits.end(); ++I) {
100234287Sdim    FileEdit &FA = I->second;
101234287Sdim    FileOffset B = I->first;
102234287Sdim    FileOffset E = B.getWithOffset(FA.RemoveLen);
103234287Sdim
104245431Sdim    if (BeginOffs == B)
105245431Sdim      break;
106245431Sdim
107234287Sdim    if (BeginOffs < E) {
108245431Sdim      if (BeginOffs > B) {
109234287Sdim        BeginOffs = E;
110234287Sdim        ++I;
111234287Sdim      }
112234287Sdim      break;
113234287Sdim    }
114234287Sdim  }
115234287Sdim
116234287Sdim  for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
117234287Sdim    FileEdit &FA = I->second;
118234287Sdim    FileOffset B = I->first;
119234287Sdim    FileOffset E = B.getWithOffset(FA.RemoveLen);
120234287Sdim
121234287Sdim    if (BeginOffs < B) {
122234287Sdim      bool Invalid = false;
123234287Sdim      StringRef text = getSourceText(BeginOffs, B, Invalid);
124234287Sdim      if (Invalid)
125234287Sdim        return false;
126234287Sdim      StrVec += text;
127234287Sdim    }
128234287Sdim    StrVec += FA.Text;
129234287Sdim    BeginOffs = E;
130234287Sdim  }
131234287Sdim
132234287Sdim  if (BeginOffs < EndOffs) {
133234287Sdim    bool Invalid = false;
134234287Sdim    StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
135234287Sdim    if (Invalid)
136234287Sdim      return false;
137234287Sdim    StrVec += text;
138234287Sdim  }
139234287Sdim
140234287Sdim  return commitInsert(OrigLoc, Offs, StrVec.str(), beforePreviousInsertions);
141234287Sdim}
142234287Sdim
143234287Sdimvoid EditedSource::commitRemove(SourceLocation OrigLoc,
144234287Sdim                                FileOffset BeginOffs, unsigned Len) {
145234287Sdim  if (Len == 0)
146234287Sdim    return;
147234287Sdim
148234287Sdim  FileOffset EndOffs = BeginOffs.getWithOffset(Len);
149234287Sdim  FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
150234287Sdim  if (I != FileEdits.begin())
151234287Sdim    --I;
152234287Sdim
153234287Sdim  for (; I != FileEdits.end(); ++I) {
154234287Sdim    FileEdit &FA = I->second;
155234287Sdim    FileOffset B = I->first;
156234287Sdim    FileOffset E = B.getWithOffset(FA.RemoveLen);
157234287Sdim
158234287Sdim    if (BeginOffs < E)
159234287Sdim      break;
160234287Sdim  }
161234287Sdim
162234287Sdim  FileOffset TopBegin, TopEnd;
163234287Sdim  FileEdit *TopFA = 0;
164234287Sdim
165234287Sdim  if (I == FileEdits.end()) {
166234287Sdim    FileEditsTy::iterator
167234287Sdim      NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
168234287Sdim    NewI->second.RemoveLen = Len;
169234287Sdim    return;
170234287Sdim  }
171234287Sdim
172234287Sdim  FileEdit &FA = I->second;
173234287Sdim  FileOffset B = I->first;
174234287Sdim  FileOffset E = B.getWithOffset(FA.RemoveLen);
175234287Sdim  if (BeginOffs < B) {
176234287Sdim    FileEditsTy::iterator
177234287Sdim      NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
178234287Sdim    TopBegin = BeginOffs;
179234287Sdim    TopEnd = EndOffs;
180234287Sdim    TopFA = &NewI->second;
181234287Sdim    TopFA->RemoveLen = Len;
182234287Sdim  } else {
183234287Sdim    TopBegin = B;
184234287Sdim    TopEnd = E;
185234287Sdim    TopFA = &I->second;
186234287Sdim    if (TopEnd >= EndOffs)
187234287Sdim      return;
188234287Sdim    unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
189234287Sdim    TopEnd = EndOffs;
190234287Sdim    TopFA->RemoveLen += diff;
191252723Sdim    if (B == BeginOffs)
192252723Sdim      TopFA->Text = StringRef();
193234287Sdim    ++I;
194234287Sdim  }
195234287Sdim
196234287Sdim  while (I != FileEdits.end()) {
197234287Sdim    FileEdit &FA = I->second;
198234287Sdim    FileOffset B = I->first;
199234287Sdim    FileOffset E = B.getWithOffset(FA.RemoveLen);
200234287Sdim
201234287Sdim    if (B >= TopEnd)
202234287Sdim      break;
203234287Sdim
204234287Sdim    if (E <= TopEnd) {
205234287Sdim      FileEdits.erase(I++);
206234287Sdim      continue;
207234287Sdim    }
208234287Sdim
209234287Sdim    if (B < TopEnd) {
210234287Sdim      unsigned diff = E.getOffset() - TopEnd.getOffset();
211234287Sdim      TopEnd = E;
212234287Sdim      TopFA->RemoveLen += diff;
213234287Sdim      FileEdits.erase(I);
214234287Sdim    }
215234287Sdim
216234287Sdim    break;
217234287Sdim  }
218234287Sdim}
219234287Sdim
220234287Sdimbool EditedSource::commit(const Commit &commit) {
221234287Sdim  if (!commit.isCommitable())
222234287Sdim    return false;
223234287Sdim
224234287Sdim  for (edit::Commit::edit_iterator
225234287Sdim         I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
226234287Sdim    const edit::Commit::Edit &edit = *I;
227234287Sdim    switch (edit.Kind) {
228234287Sdim    case edit::Commit::Act_Insert:
229234287Sdim      commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
230234287Sdim      break;
231234287Sdim    case edit::Commit::Act_InsertFromRange:
232234287Sdim      commitInsertFromRange(edit.OrigLoc, edit.Offset,
233234287Sdim                            edit.InsertFromRangeOffs, edit.Length,
234234287Sdim                            edit.BeforePrev);
235234287Sdim      break;
236234287Sdim    case edit::Commit::Act_Remove:
237234287Sdim      commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
238234287Sdim      break;
239234287Sdim    }
240234287Sdim  }
241234287Sdim
242234287Sdim  return true;
243234287Sdim}
244234287Sdim
245252723Sdim// \brief Returns true if it is ok to make the two given characters adjacent.
246252723Sdimstatic bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
247252723Sdim  // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
248252723Sdim  // making two '<' adjacent.
249252723Sdim  return !(Lexer::isIdentifierBodyChar(left, LangOpts) &&
250252723Sdim           Lexer::isIdentifierBodyChar(right, LangOpts));
251252723Sdim}
252252723Sdim
253252723Sdim/// \brief Returns true if it is ok to eliminate the trailing whitespace between
254252723Sdim/// the given characters.
255252723Sdimstatic bool canRemoveWhitespace(char left, char beforeWSpace, char right,
256252723Sdim                                const LangOptions &LangOpts) {
257252723Sdim  if (!canBeJoined(left, right, LangOpts))
258252723Sdim    return false;
259252723Sdim  if (isWhitespace(left) || isWhitespace(right))
260252723Sdim    return true;
261252723Sdim  if (canBeJoined(beforeWSpace, right, LangOpts))
262252723Sdim    return false; // the whitespace was intentional, keep it.
263252723Sdim  return true;
264252723Sdim}
265252723Sdim
266252723Sdim/// \brief Check the range that we are going to remove and:
267252723Sdim/// -Remove any trailing whitespace if possible.
268252723Sdim/// -Insert a space if removing the range is going to mess up the source tokens.
269252723Sdimstatic void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
270252723Sdim                          SourceLocation Loc, FileOffset offs,
271252723Sdim                          unsigned &len, StringRef &text) {
272252723Sdim  assert(len && text.empty());
273252723Sdim  SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
274252723Sdim  if (BeginTokLoc != Loc)
275252723Sdim    return; // the range is not at the beginning of a token, keep the range.
276252723Sdim
277252723Sdim  bool Invalid = false;
278252723Sdim  StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
279252723Sdim  if (Invalid)
280252723Sdim    return;
281252723Sdim
282252723Sdim  unsigned begin = offs.getOffset();
283252723Sdim  unsigned end = begin + len;
284252723Sdim
285252723Sdim  // FIXME: Remove newline.
286252723Sdim
287252723Sdim  if (begin == 0) {
288252723Sdim    if (buffer[end] == ' ')
289252723Sdim      ++len;
290252723Sdim    return;
291252723Sdim  }
292252723Sdim
293252723Sdim  if (buffer[end] == ' ') {
294252723Sdim    if (canRemoveWhitespace(/*left=*/buffer[begin-1],
295252723Sdim                            /*beforeWSpace=*/buffer[end-1],
296252723Sdim                            /*right=*/buffer[end+1],
297252723Sdim                            LangOpts))
298252723Sdim      ++len;
299252723Sdim    return;
300252723Sdim  }
301252723Sdim
302252723Sdim  if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
303252723Sdim    text = " ";
304252723Sdim}
305252723Sdim
306234287Sdimstatic void applyRewrite(EditsReceiver &receiver,
307234287Sdim                         StringRef text, FileOffset offs, unsigned len,
308252723Sdim                         const SourceManager &SM, const LangOptions &LangOpts) {
309234287Sdim  assert(!offs.getFID().isInvalid());
310234287Sdim  SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
311234287Sdim  Loc = Loc.getLocWithOffset(offs.getOffset());
312234287Sdim  assert(Loc.isFileID());
313252723Sdim
314252723Sdim  if (text.empty())
315252723Sdim    adjustRemoval(SM, LangOpts, Loc, offs, len, text);
316252723Sdim
317234287Sdim  CharSourceRange range = CharSourceRange::getCharRange(Loc,
318234287Sdim                                                     Loc.getLocWithOffset(len));
319234287Sdim
320234287Sdim  if (text.empty()) {
321234287Sdim    assert(len);
322234287Sdim    receiver.remove(range);
323234287Sdim    return;
324234287Sdim  }
325234287Sdim
326234287Sdim  if (len)
327234287Sdim    receiver.replace(range, text);
328234287Sdim  else
329234287Sdim    receiver.insert(Loc, text);
330234287Sdim}
331234287Sdim
332234287Sdimvoid EditedSource::applyRewrites(EditsReceiver &receiver) {
333252723Sdim  SmallString<128> StrVec;
334234287Sdim  FileOffset CurOffs, CurEnd;
335234287Sdim  unsigned CurLen;
336234287Sdim
337234287Sdim  if (FileEdits.empty())
338234287Sdim    return;
339234287Sdim
340234287Sdim  FileEditsTy::iterator I = FileEdits.begin();
341234287Sdim  CurOffs = I->first;
342234287Sdim  StrVec = I->second.Text;
343234287Sdim  CurLen = I->second.RemoveLen;
344234287Sdim  CurEnd = CurOffs.getWithOffset(CurLen);
345234287Sdim  ++I;
346234287Sdim
347234287Sdim  for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
348234287Sdim    FileOffset offs = I->first;
349234287Sdim    FileEdit act = I->second;
350234287Sdim    assert(offs >= CurEnd);
351234287Sdim
352234287Sdim    if (offs == CurEnd) {
353234287Sdim      StrVec += act.Text;
354234287Sdim      CurLen += act.RemoveLen;
355234287Sdim      CurEnd.getWithOffset(act.RemoveLen);
356234287Sdim      continue;
357234287Sdim    }
358234287Sdim
359252723Sdim    applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
360234287Sdim    CurOffs = offs;
361234287Sdim    StrVec = act.Text;
362234287Sdim    CurLen = act.RemoveLen;
363234287Sdim    CurEnd = CurOffs.getWithOffset(CurLen);
364234287Sdim  }
365234287Sdim
366252723Sdim  applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
367234287Sdim}
368234287Sdim
369234287Sdimvoid EditedSource::clearRewrites() {
370234287Sdim  FileEdits.clear();
371234287Sdim  StrAlloc.Reset();
372234287Sdim}
373234287Sdim
374234287SdimStringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
375234287Sdim                                      bool &Invalid) {
376234287Sdim  assert(BeginOffs.getFID() == EndOffs.getFID());
377234287Sdim  assert(BeginOffs <= EndOffs);
378234287Sdim  SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
379234287Sdim  BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
380234287Sdim  assert(BLoc.isFileID());
381234287Sdim  SourceLocation
382234287Sdim    ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
383234287Sdim  return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
384234287Sdim                              SourceMgr, LangOpts, &Invalid);
385234287Sdim}
386234287Sdim
387234287SdimEditedSource::FileEditsTy::iterator
388234287SdimEditedSource::getActionForOffset(FileOffset Offs) {
389234287Sdim  FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
390234287Sdim  if (I == FileEdits.begin())
391234287Sdim    return FileEdits.end();
392234287Sdim  --I;
393234287Sdim  FileEdit &FA = I->second;
394234287Sdim  FileOffset B = I->first;
395234287Sdim  FileOffset E = B.getWithOffset(FA.RemoveLen);
396234287Sdim  if (Offs >= B && Offs < E)
397234287Sdim    return I;
398234287Sdim
399234287Sdim  return FileEdits.end();
400234287Sdim}
401