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