Commit.cpp revision 1921b58a2aba4f5073d6634d476356b6a4ff8815
1//===----- Commit.cpp - A unit of 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/Commit.h"
11#include "clang/Basic/SourceManager.h"
12#include "clang/Edit/EditedSource.h"
13#include "clang/Lex/Lexer.h"
14#include "clang/Lex/PPConditionalDirectiveRecord.h"
15
16using namespace clang;
17using namespace edit;
18
19SourceLocation Commit::Edit::getFileLocation(SourceManager &SM) const {
20  SourceLocation Loc = SM.getLocForStartOfFile(Offset.getFID());
21  Loc = Loc.getLocWithOffset(Offset.getOffset());
22  assert(Loc.isFileID());
23  return Loc;
24}
25
26CharSourceRange Commit::Edit::getFileRange(SourceManager &SM) const {
27  SourceLocation Loc = getFileLocation(SM);
28  return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length));
29}
30
31CharSourceRange Commit::Edit::getInsertFromRange(SourceManager &SM) const {
32  SourceLocation Loc = SM.getLocForStartOfFile(InsertFromRangeOffs.getFID());
33  Loc = Loc.getLocWithOffset(InsertFromRangeOffs.getOffset());
34  assert(Loc.isFileID());
35  return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length));
36}
37
38Commit::Commit(EditedSource &Editor)
39  : SourceMgr(Editor.getSourceManager()), LangOpts(Editor.getLangOpts()),
40    PPRec(Editor.getPPCondDirectiveRecord()),
41    Editor(&Editor), IsCommitable(true) { }
42
43bool Commit::insert(SourceLocation loc, StringRef text,
44                    bool afterToken, bool beforePreviousInsertions) {
45  if (text.empty())
46    return true;
47
48  FileOffset Offs;
49  if ((!afterToken && !canInsert(loc, Offs)) ||
50      ( afterToken && !canInsertAfterToken(loc, Offs, loc))) {
51    IsCommitable = false;
52    return false;
53  }
54
55  addInsert(loc, Offs, text, beforePreviousInsertions);
56  return true;
57}
58
59bool Commit::insertFromRange(SourceLocation loc,
60                             CharSourceRange range,
61                             bool afterToken, bool beforePreviousInsertions) {
62  FileOffset RangeOffs;
63  unsigned RangeLen;
64  if (!canRemoveRange(range, RangeOffs, RangeLen)) {
65    IsCommitable = false;
66    return false;
67  }
68
69  FileOffset Offs;
70  if ((!afterToken && !canInsert(loc, Offs)) ||
71      ( afterToken && !canInsertAfterToken(loc, Offs, loc))) {
72    IsCommitable = false;
73    return false;
74  }
75
76  if (PPRec &&
77      PPRec->areInDifferentConditionalDirectiveRegion(loc, range.getBegin())) {
78    IsCommitable = false;
79    return false;
80  }
81
82  addInsertFromRange(loc, Offs, RangeOffs, RangeLen, beforePreviousInsertions);
83  return true;
84}
85
86bool Commit::remove(CharSourceRange range) {
87  FileOffset Offs;
88  unsigned Len;
89  if (!canRemoveRange(range, Offs, Len)) {
90    IsCommitable = false;
91    return false;
92  }
93
94  addRemove(range.getBegin(), Offs, Len);
95  return true;
96}
97
98bool Commit::insertWrap(StringRef before, CharSourceRange range,
99                        StringRef after) {
100  bool commitableBefore = insert(range.getBegin(), before, /*afterToken=*/false,
101                                 /*beforePreviousInsertions=*/true);
102  bool commitableAfter;
103  if (range.isTokenRange())
104    commitableAfter = insertAfterToken(range.getEnd(), after);
105  else
106    commitableAfter = insert(range.getEnd(), after);
107
108  return commitableBefore && commitableAfter;
109}
110
111bool Commit::replace(CharSourceRange range, StringRef text) {
112  if (text.empty())
113    return remove(range);
114
115  FileOffset Offs;
116  unsigned Len;
117  if (!canInsert(range.getBegin(), Offs) || !canRemoveRange(range, Offs, Len)) {
118    IsCommitable = false;
119    return false;
120  }
121
122  addRemove(range.getBegin(), Offs, Len);
123  addInsert(range.getBegin(), Offs, text, false);
124  return true;
125}
126
127bool Commit::replaceWithInner(CharSourceRange range,
128                              CharSourceRange replacementRange) {
129  FileOffset OuterBegin;
130  unsigned OuterLen;
131  if (!canRemoveRange(range, OuterBegin, OuterLen)) {
132    IsCommitable = false;
133    return false;
134  }
135
136  FileOffset InnerBegin;
137  unsigned InnerLen;
138  if (!canRemoveRange(replacementRange, InnerBegin, InnerLen)) {
139    IsCommitable = false;
140    return false;
141  }
142
143  FileOffset OuterEnd = OuterBegin.getWithOffset(OuterLen);
144  FileOffset InnerEnd = InnerBegin.getWithOffset(InnerLen);
145  if (OuterBegin.getFID() != InnerBegin.getFID() ||
146      InnerBegin < OuterBegin ||
147      InnerBegin > OuterEnd ||
148      InnerEnd > OuterEnd) {
149    IsCommitable = false;
150    return false;
151  }
152
153  addRemove(range.getBegin(),
154            OuterBegin, InnerBegin.getOffset() - OuterBegin.getOffset());
155  addRemove(replacementRange.getEnd(),
156            InnerEnd, OuterEnd.getOffset() - InnerEnd.getOffset());
157  return true;
158}
159
160bool Commit::replaceText(SourceLocation loc, StringRef text,
161                         StringRef replacementText) {
162  if (text.empty() || replacementText.empty())
163    return true;
164
165  FileOffset Offs;
166  unsigned Len;
167  if (!canReplaceText(loc, replacementText, Offs, Len)) {
168    IsCommitable = false;
169    return false;
170  }
171
172  addRemove(loc, Offs, Len);
173  addInsert(loc, Offs, text, false);
174  return true;
175}
176
177void Commit::addInsert(SourceLocation OrigLoc, FileOffset Offs, StringRef text,
178                       bool beforePreviousInsertions) {
179  if (text.empty())
180    return;
181
182  Edit data;
183  data.Kind = Act_Insert;
184  data.OrigLoc = OrigLoc;
185  data.Offset = Offs;
186  data.Text = copyString(text);
187  data.BeforePrev = beforePreviousInsertions;
188  CachedEdits.push_back(data);
189}
190
191void Commit::addInsertFromRange(SourceLocation OrigLoc, FileOffset Offs,
192                                FileOffset RangeOffs, unsigned RangeLen,
193                                bool beforePreviousInsertions) {
194  if (RangeLen == 0)
195    return;
196
197  Edit data;
198  data.Kind = Act_InsertFromRange;
199  data.OrigLoc = OrigLoc;
200  data.Offset = Offs;
201  data.InsertFromRangeOffs = RangeOffs;
202  data.Length = RangeLen;
203  data.BeforePrev = beforePreviousInsertions;
204  CachedEdits.push_back(data);
205}
206
207void Commit::addRemove(SourceLocation OrigLoc,
208                       FileOffset Offs, unsigned Len) {
209  if (Len == 0)
210    return;
211
212  Edit data;
213  data.Kind = Act_Remove;
214  data.OrigLoc = OrigLoc;
215  data.Offset = Offs;
216  data.Length = Len;
217  CachedEdits.push_back(data);
218}
219
220bool Commit::canInsert(SourceLocation loc, FileOffset &offs) {
221  if (loc.isInvalid())
222    return false;
223
224  if (loc.isMacroID())
225    isAtStartOfMacroExpansion(loc, &loc);
226
227  const SourceManager &SM = SourceMgr;
228  while (SM.isMacroArgExpansion(loc))
229    loc = SM.getImmediateSpellingLoc(loc);
230
231  if (loc.isMacroID())
232    if (!isAtStartOfMacroExpansion(loc, &loc))
233      return false;
234
235  if (SM.isInSystemHeader(loc))
236    return false;
237
238  std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc);
239  if (locInfo.first.isInvalid())
240    return false;
241  offs = FileOffset(locInfo.first, locInfo.second);
242  return canInsertInOffset(loc, offs);
243}
244
245bool Commit::canInsertAfterToken(SourceLocation loc, FileOffset &offs,
246                                 SourceLocation &AfterLoc) {
247  if (loc.isInvalid())
248
249    return false;
250
251  SourceLocation spellLoc = SourceMgr.getSpellingLoc(loc);
252  unsigned tokLen = Lexer::MeasureTokenLength(spellLoc, SourceMgr, LangOpts);
253  AfterLoc = loc.getLocWithOffset(tokLen);
254
255  if (loc.isMacroID())
256    isAtEndOfMacroExpansion(loc, &loc);
257
258  const SourceManager &SM = SourceMgr;
259  while (SM.isMacroArgExpansion(loc))
260    loc = SM.getImmediateSpellingLoc(loc);
261
262  if (loc.isMacroID())
263    if (!isAtEndOfMacroExpansion(loc, &loc))
264      return false;
265
266  if (SM.isInSystemHeader(loc))
267    return false;
268
269  loc = Lexer::getLocForEndOfToken(loc, 0, SourceMgr, LangOpts);
270  if (loc.isInvalid())
271    return false;
272
273  std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc);
274  if (locInfo.first.isInvalid())
275    return false;
276  offs = FileOffset(locInfo.first, locInfo.second);
277  return canInsertInOffset(loc, offs);
278}
279
280bool Commit::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
281  for (unsigned i = 0, e = CachedEdits.size(); i != e; ++i) {
282    Edit &act = CachedEdits[i];
283    if (act.Kind == Act_Remove) {
284      if (act.Offset.getFID() == Offs.getFID() &&
285          Offs > act.Offset && Offs < act.Offset.getWithOffset(act.Length))
286        return false; // position has been removed.
287    }
288  }
289
290  if (!Editor)
291    return true;
292  return Editor->canInsertInOffset(OrigLoc, Offs);
293}
294
295bool Commit::canRemoveRange(CharSourceRange range,
296                            FileOffset &Offs, unsigned &Len) {
297  const SourceManager &SM = SourceMgr;
298  range = Lexer::makeFileCharRange(range, SM, LangOpts);
299  if (range.isInvalid())
300    return false;
301
302  if (range.getBegin().isMacroID() || range.getEnd().isMacroID())
303    return false;
304  if (SM.isInSystemHeader(range.getBegin()) ||
305      SM.isInSystemHeader(range.getEnd()))
306    return false;
307
308  if (PPRec && PPRec->rangeIntersectsConditionalDirective(range.getAsRange()))
309    return false;
310
311  std::pair<FileID, unsigned> beginInfo = SM.getDecomposedLoc(range.getBegin());
312  std::pair<FileID, unsigned> endInfo = SM.getDecomposedLoc(range.getEnd());
313  if (beginInfo.first != endInfo.first ||
314      beginInfo.second > endInfo.second)
315    return false;
316
317  Offs = FileOffset(beginInfo.first, beginInfo.second);
318  Len = endInfo.second - beginInfo.second;
319  return true;
320}
321
322bool Commit::canReplaceText(SourceLocation loc, StringRef text,
323                            FileOffset &Offs, unsigned &Len) {
324  assert(!text.empty());
325
326  if (!canInsert(loc, Offs))
327    return false;
328
329  // Try to load the file buffer.
330  bool invalidTemp = false;
331  StringRef file = SourceMgr.getBufferData(Offs.getFID(), &invalidTemp);
332  if (invalidTemp)
333    return false;
334
335  Len = text.size();
336  return file.substr(Offs.getOffset()).startswith(text);
337}
338
339bool Commit::isAtStartOfMacroExpansion(SourceLocation loc,
340                                       SourceLocation *MacroBegin) const {
341  return Lexer::isAtStartOfMacroExpansion(loc, SourceMgr, LangOpts, MacroBegin);
342}
343bool Commit::isAtEndOfMacroExpansion(SourceLocation loc,
344                                     SourceLocation *MacroEnd) const {
345  return Lexer::isAtEndOfMacroExpansion(loc, SourceMgr, LangOpts, MacroEnd);
346}
347