1234287Sdim//===----- Commit.cpp - A unit of 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/Commit.h"
11249423Sdim#include "clang/Basic/SourceManager.h"
12234287Sdim#include "clang/Edit/EditedSource.h"
13234287Sdim#include "clang/Lex/Lexer.h"
14249423Sdim#include "clang/Lex/PPConditionalDirectiveRecord.h"
15234287Sdim
16234287Sdimusing namespace clang;
17234287Sdimusing namespace edit;
18234287Sdim
19234287SdimSourceLocation Commit::Edit::getFileLocation(SourceManager &SM) const {
20234287Sdim  SourceLocation Loc = SM.getLocForStartOfFile(Offset.getFID());
21234287Sdim  Loc = Loc.getLocWithOffset(Offset.getOffset());
22234287Sdim  assert(Loc.isFileID());
23234287Sdim  return Loc;
24234287Sdim}
25234287Sdim
26234287SdimCharSourceRange Commit::Edit::getFileRange(SourceManager &SM) const {
27234287Sdim  SourceLocation Loc = getFileLocation(SM);
28234287Sdim  return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length));
29234287Sdim}
30234287Sdim
31234287SdimCharSourceRange Commit::Edit::getInsertFromRange(SourceManager &SM) const {
32234287Sdim  SourceLocation Loc = SM.getLocForStartOfFile(InsertFromRangeOffs.getFID());
33234287Sdim  Loc = Loc.getLocWithOffset(InsertFromRangeOffs.getOffset());
34234287Sdim  assert(Loc.isFileID());
35234287Sdim  return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length));
36234287Sdim}
37234287Sdim
38234287SdimCommit::Commit(EditedSource &Editor)
39234287Sdim  : SourceMgr(Editor.getSourceManager()), LangOpts(Editor.getLangOpts()),
40249423Sdim    PPRec(Editor.getPPCondDirectiveRecord()),
41263508Sdim    Editor(&Editor),
42263508Sdim    ForceCommitInSystemHeader(Editor.getForceCommitInSystemHeader()),
43263508Sdim    IsCommitable(true) { }
44234287Sdim
45234287Sdimbool Commit::insert(SourceLocation loc, StringRef text,
46234287Sdim                    bool afterToken, bool beforePreviousInsertions) {
47234287Sdim  if (text.empty())
48234287Sdim    return true;
49234287Sdim
50234287Sdim  FileOffset Offs;
51234287Sdim  if ((!afterToken && !canInsert(loc, Offs)) ||
52234287Sdim      ( afterToken && !canInsertAfterToken(loc, Offs, loc))) {
53234287Sdim    IsCommitable = false;
54234287Sdim    return false;
55234287Sdim  }
56234287Sdim
57234287Sdim  addInsert(loc, Offs, text, beforePreviousInsertions);
58234287Sdim  return true;
59234287Sdim}
60234287Sdim
61234287Sdimbool Commit::insertFromRange(SourceLocation loc,
62234287Sdim                             CharSourceRange range,
63234287Sdim                             bool afterToken, bool beforePreviousInsertions) {
64234287Sdim  FileOffset RangeOffs;
65234287Sdim  unsigned RangeLen;
66234287Sdim  if (!canRemoveRange(range, RangeOffs, RangeLen)) {
67234287Sdim    IsCommitable = false;
68234287Sdim    return false;
69234287Sdim  }
70234287Sdim
71234287Sdim  FileOffset Offs;
72234287Sdim  if ((!afterToken && !canInsert(loc, Offs)) ||
73234287Sdim      ( afterToken && !canInsertAfterToken(loc, Offs, loc))) {
74234287Sdim    IsCommitable = false;
75234287Sdim    return false;
76234287Sdim  }
77234287Sdim
78234287Sdim  if (PPRec &&
79234287Sdim      PPRec->areInDifferentConditionalDirectiveRegion(loc, range.getBegin())) {
80234287Sdim    IsCommitable = false;
81234287Sdim    return false;
82234287Sdim  }
83234287Sdim
84234287Sdim  addInsertFromRange(loc, Offs, RangeOffs, RangeLen, beforePreviousInsertions);
85234287Sdim  return true;
86234287Sdim}
87234287Sdim
88234287Sdimbool Commit::remove(CharSourceRange range) {
89234287Sdim  FileOffset Offs;
90234287Sdim  unsigned Len;
91234287Sdim  if (!canRemoveRange(range, Offs, Len)) {
92234287Sdim    IsCommitable = false;
93234287Sdim    return false;
94234287Sdim  }
95234287Sdim
96234287Sdim  addRemove(range.getBegin(), Offs, Len);
97234287Sdim  return true;
98234287Sdim}
99234287Sdim
100234287Sdimbool Commit::insertWrap(StringRef before, CharSourceRange range,
101234287Sdim                        StringRef after) {
102234287Sdim  bool commitableBefore = insert(range.getBegin(), before, /*afterToken=*/false,
103234287Sdim                                 /*beforePreviousInsertions=*/true);
104234287Sdim  bool commitableAfter;
105234287Sdim  if (range.isTokenRange())
106234287Sdim    commitableAfter = insertAfterToken(range.getEnd(), after);
107234287Sdim  else
108234287Sdim    commitableAfter = insert(range.getEnd(), after);
109234287Sdim
110234287Sdim  return commitableBefore && commitableAfter;
111234287Sdim}
112234287Sdim
113234287Sdimbool Commit::replace(CharSourceRange range, StringRef text) {
114234287Sdim  if (text.empty())
115234287Sdim    return remove(range);
116234287Sdim
117234287Sdim  FileOffset Offs;
118234287Sdim  unsigned Len;
119234287Sdim  if (!canInsert(range.getBegin(), Offs) || !canRemoveRange(range, Offs, Len)) {
120234287Sdim    IsCommitable = false;
121234287Sdim    return false;
122234287Sdim  }
123234287Sdim
124234287Sdim  addRemove(range.getBegin(), Offs, Len);
125234287Sdim  addInsert(range.getBegin(), Offs, text, false);
126234287Sdim  return true;
127234287Sdim}
128234287Sdim
129234287Sdimbool Commit::replaceWithInner(CharSourceRange range,
130234287Sdim                              CharSourceRange replacementRange) {
131234287Sdim  FileOffset OuterBegin;
132234287Sdim  unsigned OuterLen;
133234287Sdim  if (!canRemoveRange(range, OuterBegin, OuterLen)) {
134234287Sdim    IsCommitable = false;
135234287Sdim    return false;
136234287Sdim  }
137234287Sdim
138234287Sdim  FileOffset InnerBegin;
139234287Sdim  unsigned InnerLen;
140234287Sdim  if (!canRemoveRange(replacementRange, InnerBegin, InnerLen)) {
141234287Sdim    IsCommitable = false;
142234287Sdim    return false;
143234287Sdim  }
144234287Sdim
145234287Sdim  FileOffset OuterEnd = OuterBegin.getWithOffset(OuterLen);
146234287Sdim  FileOffset InnerEnd = InnerBegin.getWithOffset(InnerLen);
147234287Sdim  if (OuterBegin.getFID() != InnerBegin.getFID() ||
148234287Sdim      InnerBegin < OuterBegin ||
149234287Sdim      InnerBegin > OuterEnd ||
150234287Sdim      InnerEnd > OuterEnd) {
151234287Sdim    IsCommitable = false;
152234287Sdim    return false;
153234287Sdim  }
154234287Sdim
155234287Sdim  addRemove(range.getBegin(),
156234287Sdim            OuterBegin, InnerBegin.getOffset() - OuterBegin.getOffset());
157234287Sdim  addRemove(replacementRange.getEnd(),
158234287Sdim            InnerEnd, OuterEnd.getOffset() - InnerEnd.getOffset());
159234287Sdim  return true;
160234287Sdim}
161234287Sdim
162234287Sdimbool Commit::replaceText(SourceLocation loc, StringRef text,
163234287Sdim                         StringRef replacementText) {
164234287Sdim  if (text.empty() || replacementText.empty())
165234287Sdim    return true;
166234287Sdim
167234287Sdim  FileOffset Offs;
168234287Sdim  unsigned Len;
169234287Sdim  if (!canReplaceText(loc, replacementText, Offs, Len)) {
170234287Sdim    IsCommitable = false;
171234287Sdim    return false;
172234287Sdim  }
173234287Sdim
174234287Sdim  addRemove(loc, Offs, Len);
175234287Sdim  addInsert(loc, Offs, text, false);
176234287Sdim  return true;
177234287Sdim}
178234287Sdim
179234287Sdimvoid Commit::addInsert(SourceLocation OrigLoc, FileOffset Offs, StringRef text,
180234287Sdim                       bool beforePreviousInsertions) {
181234287Sdim  if (text.empty())
182234287Sdim    return;
183234287Sdim
184234287Sdim  Edit data;
185234287Sdim  data.Kind = Act_Insert;
186234287Sdim  data.OrigLoc = OrigLoc;
187234287Sdim  data.Offset = Offs;
188263508Sdim  data.Text = copyString(text);
189234287Sdim  data.BeforePrev = beforePreviousInsertions;
190234287Sdim  CachedEdits.push_back(data);
191234287Sdim}
192234287Sdim
193234287Sdimvoid Commit::addInsertFromRange(SourceLocation OrigLoc, FileOffset Offs,
194234287Sdim                                FileOffset RangeOffs, unsigned RangeLen,
195234287Sdim                                bool beforePreviousInsertions) {
196234287Sdim  if (RangeLen == 0)
197234287Sdim    return;
198234287Sdim
199234287Sdim  Edit data;
200234287Sdim  data.Kind = Act_InsertFromRange;
201234287Sdim  data.OrigLoc = OrigLoc;
202234287Sdim  data.Offset = Offs;
203234287Sdim  data.InsertFromRangeOffs = RangeOffs;
204234287Sdim  data.Length = RangeLen;
205234287Sdim  data.BeforePrev = beforePreviousInsertions;
206234287Sdim  CachedEdits.push_back(data);
207234287Sdim}
208234287Sdim
209234287Sdimvoid Commit::addRemove(SourceLocation OrigLoc,
210234287Sdim                       FileOffset Offs, unsigned Len) {
211234287Sdim  if (Len == 0)
212234287Sdim    return;
213234287Sdim
214234287Sdim  Edit data;
215234287Sdim  data.Kind = Act_Remove;
216234287Sdim  data.OrigLoc = OrigLoc;
217234287Sdim  data.Offset = Offs;
218234287Sdim  data.Length = Len;
219234287Sdim  CachedEdits.push_back(data);
220234287Sdim}
221234287Sdim
222234287Sdimbool Commit::canInsert(SourceLocation loc, FileOffset &offs) {
223234287Sdim  if (loc.isInvalid())
224234287Sdim    return false;
225234287Sdim
226234287Sdim  if (loc.isMacroID())
227234287Sdim    isAtStartOfMacroExpansion(loc, &loc);
228234287Sdim
229234287Sdim  const SourceManager &SM = SourceMgr;
230234287Sdim  while (SM.isMacroArgExpansion(loc))
231234287Sdim    loc = SM.getImmediateSpellingLoc(loc);
232234287Sdim
233234287Sdim  if (loc.isMacroID())
234234287Sdim    if (!isAtStartOfMacroExpansion(loc, &loc))
235234287Sdim      return false;
236234287Sdim
237263508Sdim  if (SM.isInSystemHeader(loc) && ForceCommitInSystemHeader)
238234287Sdim    return false;
239234287Sdim
240234287Sdim  std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc);
241234287Sdim  if (locInfo.first.isInvalid())
242234287Sdim    return false;
243234287Sdim  offs = FileOffset(locInfo.first, locInfo.second);
244234287Sdim  return canInsertInOffset(loc, offs);
245234287Sdim}
246234287Sdim
247234287Sdimbool Commit::canInsertAfterToken(SourceLocation loc, FileOffset &offs,
248234287Sdim                                 SourceLocation &AfterLoc) {
249234287Sdim  if (loc.isInvalid())
250234287Sdim
251234287Sdim    return false;
252234287Sdim
253234287Sdim  SourceLocation spellLoc = SourceMgr.getSpellingLoc(loc);
254234287Sdim  unsigned tokLen = Lexer::MeasureTokenLength(spellLoc, SourceMgr, LangOpts);
255234287Sdim  AfterLoc = loc.getLocWithOffset(tokLen);
256234287Sdim
257234287Sdim  if (loc.isMacroID())
258234287Sdim    isAtEndOfMacroExpansion(loc, &loc);
259234287Sdim
260234287Sdim  const SourceManager &SM = SourceMgr;
261234287Sdim  while (SM.isMacroArgExpansion(loc))
262234287Sdim    loc = SM.getImmediateSpellingLoc(loc);
263234287Sdim
264234287Sdim  if (loc.isMacroID())
265234287Sdim    if (!isAtEndOfMacroExpansion(loc, &loc))
266234287Sdim      return false;
267234287Sdim
268263508Sdim  if (SM.isInSystemHeader(loc) && ForceCommitInSystemHeader)
269234287Sdim    return false;
270234287Sdim
271234287Sdim  loc = Lexer::getLocForEndOfToken(loc, 0, SourceMgr, LangOpts);
272234287Sdim  if (loc.isInvalid())
273234287Sdim    return false;
274234287Sdim
275234287Sdim  std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc);
276234287Sdim  if (locInfo.first.isInvalid())
277234287Sdim    return false;
278234287Sdim  offs = FileOffset(locInfo.first, locInfo.second);
279234287Sdim  return canInsertInOffset(loc, offs);
280234287Sdim}
281234287Sdim
282234287Sdimbool Commit::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
283234287Sdim  for (unsigned i = 0, e = CachedEdits.size(); i != e; ++i) {
284234287Sdim    Edit &act = CachedEdits[i];
285234287Sdim    if (act.Kind == Act_Remove) {
286234287Sdim      if (act.Offset.getFID() == Offs.getFID() &&
287234287Sdim          Offs > act.Offset && Offs < act.Offset.getWithOffset(act.Length))
288234287Sdim        return false; // position has been removed.
289234287Sdim    }
290234287Sdim  }
291234287Sdim
292234287Sdim  if (!Editor)
293234287Sdim    return true;
294234287Sdim  return Editor->canInsertInOffset(OrigLoc, Offs);
295234287Sdim}
296234287Sdim
297234287Sdimbool Commit::canRemoveRange(CharSourceRange range,
298234287Sdim                            FileOffset &Offs, unsigned &Len) {
299234287Sdim  const SourceManager &SM = SourceMgr;
300234287Sdim  range = Lexer::makeFileCharRange(range, SM, LangOpts);
301234287Sdim  if (range.isInvalid())
302234287Sdim    return false;
303234287Sdim
304234287Sdim  if (range.getBegin().isMacroID() || range.getEnd().isMacroID())
305234287Sdim    return false;
306263508Sdim  if ((SM.isInSystemHeader(range.getBegin()) ||
307263508Sdim       SM.isInSystemHeader(range.getEnd())) && ForceCommitInSystemHeader)
308234287Sdim    return false;
309234287Sdim
310234287Sdim  if (PPRec && PPRec->rangeIntersectsConditionalDirective(range.getAsRange()))
311234287Sdim    return false;
312234287Sdim
313234287Sdim  std::pair<FileID, unsigned> beginInfo = SM.getDecomposedLoc(range.getBegin());
314234287Sdim  std::pair<FileID, unsigned> endInfo = SM.getDecomposedLoc(range.getEnd());
315234287Sdim  if (beginInfo.first != endInfo.first ||
316234287Sdim      beginInfo.second > endInfo.second)
317234287Sdim    return false;
318234287Sdim
319234287Sdim  Offs = FileOffset(beginInfo.first, beginInfo.second);
320234287Sdim  Len = endInfo.second - beginInfo.second;
321234287Sdim  return true;
322234287Sdim}
323234287Sdim
324234287Sdimbool Commit::canReplaceText(SourceLocation loc, StringRef text,
325234287Sdim                            FileOffset &Offs, unsigned &Len) {
326234287Sdim  assert(!text.empty());
327234287Sdim
328234287Sdim  if (!canInsert(loc, Offs))
329234287Sdim    return false;
330234287Sdim
331234287Sdim  // Try to load the file buffer.
332234287Sdim  bool invalidTemp = false;
333234287Sdim  StringRef file = SourceMgr.getBufferData(Offs.getFID(), &invalidTemp);
334234287Sdim  if (invalidTemp)
335234287Sdim    return false;
336234287Sdim
337239462Sdim  Len = text.size();
338234287Sdim  return file.substr(Offs.getOffset()).startswith(text);
339234287Sdim}
340234287Sdim
341234287Sdimbool Commit::isAtStartOfMacroExpansion(SourceLocation loc,
342234287Sdim                                       SourceLocation *MacroBegin) const {
343234287Sdim  return Lexer::isAtStartOfMacroExpansion(loc, SourceMgr, LangOpts, MacroBegin);
344234287Sdim}
345234287Sdimbool Commit::isAtEndOfMacroExpansion(SourceLocation loc,
346234287Sdim                                     SourceLocation *MacroEnd) const {
347234287Sdim  return Lexer::isAtEndOfMacroExpansion(loc, SourceMgr, LangOpts, MacroEnd);
348234287Sdim}
349