clang  9.0.0svn
Commit.cpp
Go to the documentation of this file.
1 //===- Commit.cpp - A unit of edits ---------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "clang/Edit/Commit.h"
10 #include "clang/Basic/LLVM.h"
14 #include "clang/Edit/FileOffset.h"
15 #include "clang/Lex/Lexer.h"
17 #include "llvm/ADT/StringRef.h"
18 #include <cassert>
19 #include <utility>
20 
21 using namespace clang;
22 using namespace edit;
23 
26  Loc = Loc.getLocWithOffset(Offset.getOffset());
27  assert(Loc.isFileID());
28  return Loc;
29 }
30 
34 }
35 
39  assert(Loc.isFileID());
41 }
42 
44  : SourceMgr(Editor.getSourceManager()), LangOpts(Editor.getLangOpts()),
45  PPRec(Editor.getPPCondDirectiveRecord()),
46  Editor(&Editor) {}
47 
48 bool Commit::insert(SourceLocation loc, StringRef text,
49  bool afterToken, bool beforePreviousInsertions) {
50  if (text.empty())
51  return true;
52 
53  FileOffset Offs;
54  if ((!afterToken && !canInsert(loc, Offs)) ||
55  ( afterToken && !canInsertAfterToken(loc, Offs, loc))) {
56  IsCommitable = false;
57  return false;
58  }
59 
60  addInsert(loc, Offs, text, beforePreviousInsertions);
61  return true;
62 }
63 
65  CharSourceRange range,
66  bool afterToken, bool beforePreviousInsertions) {
67  FileOffset RangeOffs;
68  unsigned RangeLen;
69  if (!canRemoveRange(range, RangeOffs, RangeLen)) {
70  IsCommitable = false;
71  return false;
72  }
73 
74  FileOffset Offs;
75  if ((!afterToken && !canInsert(loc, Offs)) ||
76  ( afterToken && !canInsertAfterToken(loc, Offs, loc))) {
77  IsCommitable = false;
78  return false;
79  }
80 
81  if (PPRec &&
83  IsCommitable = false;
84  return false;
85  }
86 
87  addInsertFromRange(loc, Offs, RangeOffs, RangeLen, beforePreviousInsertions);
88  return true;
89 }
90 
92  FileOffset Offs;
93  unsigned Len;
94  if (!canRemoveRange(range, Offs, Len)) {
95  IsCommitable = false;
96  return false;
97  }
98 
99  addRemove(range.getBegin(), Offs, Len);
100  return true;
101 }
102 
103 bool Commit::insertWrap(StringRef before, CharSourceRange range,
104  StringRef after) {
105  bool commitableBefore = insert(range.getBegin(), before, /*afterToken=*/false,
106  /*beforePreviousInsertions=*/true);
107  bool commitableAfter;
108  if (range.isTokenRange())
109  commitableAfter = insertAfterToken(range.getEnd(), after);
110  else
111  commitableAfter = insert(range.getEnd(), after);
112 
113  return commitableBefore && commitableAfter;
114 }
115 
116 bool Commit::replace(CharSourceRange range, StringRef text) {
117  if (text.empty())
118  return remove(range);
119 
120  FileOffset Offs;
121  unsigned Len;
122  if (!canInsert(range.getBegin(), Offs) || !canRemoveRange(range, Offs, Len)) {
123  IsCommitable = false;
124  return false;
125  }
126 
127  addRemove(range.getBegin(), Offs, Len);
128  addInsert(range.getBegin(), Offs, text, false);
129  return true;
130 }
131 
133  CharSourceRange replacementRange) {
134  FileOffset OuterBegin;
135  unsigned OuterLen;
136  if (!canRemoveRange(range, OuterBegin, OuterLen)) {
137  IsCommitable = false;
138  return false;
139  }
140 
141  FileOffset InnerBegin;
142  unsigned InnerLen;
143  if (!canRemoveRange(replacementRange, InnerBegin, InnerLen)) {
144  IsCommitable = false;
145  return false;
146  }
147 
148  FileOffset OuterEnd = OuterBegin.getWithOffset(OuterLen);
149  FileOffset InnerEnd = InnerBegin.getWithOffset(InnerLen);
150  if (OuterBegin.getFID() != InnerBegin.getFID() ||
151  InnerBegin < OuterBegin ||
152  InnerBegin > OuterEnd ||
153  InnerEnd > OuterEnd) {
154  IsCommitable = false;
155  return false;
156  }
157 
158  addRemove(range.getBegin(),
159  OuterBegin, InnerBegin.getOffset() - OuterBegin.getOffset());
160  addRemove(replacementRange.getEnd(),
161  InnerEnd, OuterEnd.getOffset() - InnerEnd.getOffset());
162  return true;
163 }
164 
166  StringRef replacementText) {
167  if (text.empty() || replacementText.empty())
168  return true;
169 
170  FileOffset Offs;
171  unsigned Len;
172  if (!canReplaceText(loc, replacementText, Offs, Len)) {
173  IsCommitable = false;
174  return false;
175  }
176 
177  addRemove(loc, Offs, Len);
178  addInsert(loc, Offs, text, false);
179  return true;
180 }
181 
182 void Commit::addInsert(SourceLocation OrigLoc, FileOffset Offs, StringRef text,
183  bool beforePreviousInsertions) {
184  if (text.empty())
185  return;
186 
187  Edit data;
188  data.Kind = Act_Insert;
189  data.OrigLoc = OrigLoc;
190  data.Offset = Offs;
191  data.Text = text.copy(StrAlloc);
192  data.BeforePrev = beforePreviousInsertions;
193  CachedEdits.push_back(data);
194 }
195 
196 void Commit::addInsertFromRange(SourceLocation OrigLoc, FileOffset Offs,
197  FileOffset RangeOffs, unsigned RangeLen,
198  bool beforePreviousInsertions) {
199  if (RangeLen == 0)
200  return;
201 
202  Edit data;
203  data.Kind = Act_InsertFromRange;
204  data.OrigLoc = OrigLoc;
205  data.Offset = Offs;
206  data.InsertFromRangeOffs = RangeOffs;
207  data.Length = RangeLen;
208  data.BeforePrev = beforePreviousInsertions;
209  CachedEdits.push_back(data);
210 }
211 
212 void Commit::addRemove(SourceLocation OrigLoc,
213  FileOffset Offs, unsigned Len) {
214  if (Len == 0)
215  return;
216 
217  Edit data;
218  data.Kind = Act_Remove;
219  data.OrigLoc = OrigLoc;
220  data.Offset = Offs;
221  data.Length = Len;
222  CachedEdits.push_back(data);
223 }
224 
225 bool Commit::canInsert(SourceLocation loc, FileOffset &offs) {
226  if (loc.isInvalid())
227  return false;
228 
229  if (loc.isMacroID())
230  isAtStartOfMacroExpansion(loc, &loc);
231 
232  const SourceManager &SM = SourceMgr;
233  loc = SM.getTopMacroCallerLoc(loc);
234 
235  if (loc.isMacroID())
236  if (!isAtStartOfMacroExpansion(loc, &loc))
237  return false;
238 
239  if (SM.isInSystemHeader(loc))
240  return false;
241 
242  std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc);
243  if (locInfo.first.isInvalid())
244  return false;
245  offs = FileOffset(locInfo.first, locInfo.second);
246  return canInsertInOffset(loc, offs);
247 }
248 
249 bool Commit::canInsertAfterToken(SourceLocation loc, FileOffset &offs,
250  SourceLocation &AfterLoc) {
251  if (loc.isInvalid())
252 
253  return false;
254 
255  SourceLocation spellLoc = SourceMgr.getSpellingLoc(loc);
256  unsigned tokLen = Lexer::MeasureTokenLength(spellLoc, SourceMgr, LangOpts);
257  AfterLoc = loc.getLocWithOffset(tokLen);
258 
259  if (loc.isMacroID())
260  isAtEndOfMacroExpansion(loc, &loc);
261 
262  const SourceManager &SM = SourceMgr;
263  loc = SM.getTopMacroCallerLoc(loc);
264 
265  if (loc.isMacroID())
266  if (!isAtEndOfMacroExpansion(loc, &loc))
267  return false;
268 
269  if (SM.isInSystemHeader(loc))
270  return false;
271 
272  loc = Lexer::getLocForEndOfToken(loc, 0, SourceMgr, LangOpts);
273  if (loc.isInvalid())
274  return false;
275 
276  std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc);
277  if (locInfo.first.isInvalid())
278  return false;
279  offs = FileOffset(locInfo.first, locInfo.second);
280  return canInsertInOffset(loc, offs);
281 }
282 
283 bool Commit::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
284  for (const auto &act : CachedEdits)
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  if (!Editor)
292  return true;
293  return Editor->canInsertInOffset(OrigLoc, Offs);
294 }
295 
296 bool Commit::canRemoveRange(CharSourceRange range,
297  FileOffset &Offs, unsigned &Len) {
298  const SourceManager &SM = SourceMgr;
299  range = Lexer::makeFileCharRange(range, SM, LangOpts);
300  if (range.isInvalid())
301  return false;
302 
303  if (range.getBegin().isMacroID() || range.getEnd().isMacroID())
304  return false;
305  if (SM.isInSystemHeader(range.getBegin()) ||
306  SM.isInSystemHeader(range.getEnd()))
307  return false;
308 
309  if (PPRec && PPRec->rangeIntersectsConditionalDirective(range.getAsRange()))
310  return false;
311 
312  std::pair<FileID, unsigned> beginInfo = SM.getDecomposedLoc(range.getBegin());
313  std::pair<FileID, unsigned> endInfo = SM.getDecomposedLoc(range.getEnd());
314  if (beginInfo.first != endInfo.first ||
315  beginInfo.second > endInfo.second)
316  return false;
317 
318  Offs = FileOffset(beginInfo.first, beginInfo.second);
319  Len = endInfo.second - beginInfo.second;
320  return true;
321 }
322 
323 bool Commit::canReplaceText(SourceLocation loc, StringRef text,
324  FileOffset &Offs, unsigned &Len) {
325  assert(!text.empty());
326 
327  if (!canInsert(loc, Offs))
328  return false;
329 
330  // Try to load the file buffer.
331  bool invalidTemp = false;
332  StringRef file = SourceMgr.getBufferData(Offs.getFID(), &invalidTemp);
333  if (invalidTemp)
334  return false;
335 
336  Len = text.size();
337  return file.substr(Offs.getOffset()).startswith(text);
338 }
339 
340 bool Commit::isAtStartOfMacroExpansion(SourceLocation loc,
341  SourceLocation *MacroBegin) const {
342  return Lexer::isAtStartOfMacroExpansion(loc, SourceMgr, LangOpts, MacroBegin);
343 }
344 
345 bool Commit::isAtEndOfMacroExpansion(SourceLocation loc,
346  SourceLocation *MacroEnd) const {
347  return Lexer::isAtEndOfMacroExpansion(loc, SourceMgr, LangOpts, MacroEnd);
348 }
SourceLocation getLocForStartOfFile(FileID FID) const
Return the source location corresponding to the first byte of the specified file. ...
bool remove(CharSourceRange range)
Definition: Commit.cpp:91
SourceLocation getLocWithOffset(int Offset) const
Return a source location with the specified offset from this SourceLocation.
bool insertWrap(StringRef before, CharSourceRange range, StringRef after)
Definition: Commit.cpp:103
Defines the SourceManager interface.
bool insertAfterToken(SourceLocation loc, StringRef text, bool beforePreviousInsertions=false)
Definition: Commit.h:73
bool insertFromRange(SourceLocation loc, CharSourceRange range, bool afterToken=false, bool beforePreviousInsertions=false)
Definition: Commit.cpp:64
StringRef getBufferData(FileID FID, bool *Invalid=nullptr) const
Return a StringRef to the source buffer data for the specified FileID.
static bool isAtStartOfMacroExpansion(SourceLocation loc, const SourceManager &SM, const LangOptions &LangOpts, SourceLocation *MacroBegin=nullptr)
Returns true if the given MacroID location points at the first token of the macro expansion...
Definition: Lexer.cpp:792
bool insert(SourceLocation loc, StringRef text, bool afterToken=false, bool beforePreviousInsertions=false)
Definition: Commit.cpp:48
SourceLocation getBegin() const
bool replace(CharSourceRange range, StringRef text)
Definition: Commit.cpp:116
FileID getFID() const
Definition: FileOffset.h:28
Forward-declares and imports various common LLVM datatypes that clang wants to use unqualified...
bool areInDifferentConditionalDirectiveRegion(SourceLocation LHS, SourceLocation RHS) const
Returns true if the given locations are in different regions, separated by conditional directive bloc...
SourceLocation getSpellingLoc(SourceLocation Loc) const
Given a SourceLocation object, return the spelling location referenced by the ID. ...
SourceLocation OrigLoc
Definition: Commit.h:40
CharSourceRange getFileRange(SourceManager &SM) const
Definition: Commit.cpp:31
static bool isAtEndOfMacroExpansion(SourceLocation loc, const SourceManager &SM, const LangOptions &LangOpts, SourceLocation *MacroEnd=nullptr)
Returns true if the given MacroID location points at the last token of the macro expansion.
Definition: Lexer.cpp:814
static SourceLocation getLocForEndOfToken(SourceLocation Loc, unsigned Offset, const SourceManager &SM, const LangOptions &LangOpts)
Computes the source location just past the end of the token at this source location.
Definition: Lexer.cpp:770
Represents a character-granular source range.
bool isInSystemHeader(SourceLocation Loc) const
Returns if a SourceLocation is in a system header.
static unsigned MeasureTokenLength(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts)
MeasureTokenLength - Relex the token at the specified location and return its length in bytes in the ...
Definition: Lexer.cpp:435
FileOffset InsertFromRangeOffs
Definition: Commit.h:42
const SourceManager & SM
Definition: Format.cpp:1568
FileOffset getWithOffset(unsigned offset) const
Definition: FileOffset.h:31
static CharSourceRange getCharRange(SourceRange R)
Encodes a location in the source.
bool rangeIntersectsConditionalDirective(SourceRange Range) const
Returns true if the given range intersects with a conditional directive.
bool isTokenRange() const
Return true if the end of this range specifies the start of the last token.
SourceRange getAsRange() const
Dataflow Directional Tag Classes.
static CharSourceRange makeFileCharRange(CharSourceRange Range, const SourceManager &SM, const LangOptions &LangOpts)
Accepts a range and returns a character range with file locations.
Definition: Lexer.cpp:867
CharSourceRange getInsertFromRange(SourceManager &SM) const
Definition: Commit.cpp:36
unsigned getOffset() const
Definition: FileOffset.h:29
bool replaceText(SourceLocation loc, StringRef text, StringRef replacementText)
Definition: Commit.cpp:165
bool isMacroID() const
Commit(EditedSource &Editor)
Definition: Commit.cpp:43
SourceLocation getEnd() const
bool canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs)
Defines the clang::SourceLocation class and associated facilities.
SourceLocation getFileLocation(SourceManager &SM) const
Definition: Commit.cpp:24
bool replaceWithInner(CharSourceRange range, CharSourceRange innerRange)
Definition: Commit.cpp:132
This class handles loading and caching of source files into memory.
std::pair< FileID, unsigned > getDecomposedLoc(SourceLocation Loc) const
Decompose the specified location into a raw FileID + Offset pair.
SourceLocation getTopMacroCallerLoc(SourceLocation Loc) const