clang  3.7.0
EditedSource.cpp
Go to the documentation of this file.
1 //===----- EditedSource.cpp - Collection of source 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 
11 #include "clang/Basic/CharInfo.h"
13 #include "clang/Edit/Commit.h"
15 #include "clang/Lex/Lexer.h"
16 #include "llvm/ADT/SmallString.h"
17 #include "llvm/ADT/Twine.h"
18 
19 using namespace clang;
20 using namespace edit;
21 
23  replace(range, StringRef());
24 }
25 
26 StringRef EditedSource::copyString(const Twine &twine) {
27  SmallString<128> Data;
28  return copyString(twine.toStringRef(Data));
29 }
30 
32  FileEditsTy::iterator FA = getActionForOffset(Offs);
33  if (FA != FileEdits.end()) {
34  if (FA->first != Offs)
35  return false; // position has been removed.
36  }
37 
38  if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
40  DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
42  ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
43  llvm::DenseMap<unsigned, SourceLocation>::iterator
44  I = ExpansionToArgMap.find(ExpLoc.getRawEncoding());
45  if (I != ExpansionToArgMap.end() && I->second != DefArgLoc)
46  return false; // Trying to write in a macro argument input that has
47  // already been written for another argument of the same macro.
48  }
49 
50  return true;
51 }
52 
53 bool EditedSource::commitInsert(SourceLocation OrigLoc,
54  FileOffset Offs, StringRef text,
55  bool beforePreviousInsertions) {
56  if (!canInsertInOffset(OrigLoc, Offs))
57  return false;
58  if (text.empty())
59  return true;
60 
61  if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
63  DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
65  ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
66  ExpansionToArgMap[ExpLoc.getRawEncoding()] = DefArgLoc;
67  }
68 
69  FileEdit &FA = FileEdits[Offs];
70  if (FA.Text.empty()) {
71  FA.Text = copyString(text);
72  return true;
73  }
74 
75  if (beforePreviousInsertions)
76  FA.Text = copyString(Twine(text) + FA.Text);
77  else
78  FA.Text = copyString(Twine(FA.Text) + text);
79 
80  return true;
81 }
82 
83 bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
84  FileOffset Offs,
85  FileOffset InsertFromRangeOffs, unsigned Len,
86  bool beforePreviousInsertions) {
87  if (Len == 0)
88  return true;
89 
90  SmallString<128> StrVec;
91  FileOffset BeginOffs = InsertFromRangeOffs;
92  FileOffset EndOffs = BeginOffs.getWithOffset(Len);
93  FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
94  if (I != FileEdits.begin())
95  --I;
96 
97  for (; I != FileEdits.end(); ++I) {
98  FileEdit &FA = I->second;
99  FileOffset B = I->first;
100  FileOffset E = B.getWithOffset(FA.RemoveLen);
101 
102  if (BeginOffs == B)
103  break;
104 
105  if (BeginOffs < E) {
106  if (BeginOffs > B) {
107  BeginOffs = E;
108  ++I;
109  }
110  break;
111  }
112  }
113 
114  for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
115  FileEdit &FA = I->second;
116  FileOffset B = I->first;
117  FileOffset E = B.getWithOffset(FA.RemoveLen);
118 
119  if (BeginOffs < B) {
120  bool Invalid = false;
121  StringRef text = getSourceText(BeginOffs, B, Invalid);
122  if (Invalid)
123  return false;
124  StrVec += text;
125  }
126  StrVec += FA.Text;
127  BeginOffs = E;
128  }
129 
130  if (BeginOffs < EndOffs) {
131  bool Invalid = false;
132  StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
133  if (Invalid)
134  return false;
135  StrVec += text;
136  }
137 
138  return commitInsert(OrigLoc, Offs, StrVec, beforePreviousInsertions);
139 }
140 
141 void EditedSource::commitRemove(SourceLocation OrigLoc,
142  FileOffset BeginOffs, unsigned Len) {
143  if (Len == 0)
144  return;
145 
146  FileOffset EndOffs = BeginOffs.getWithOffset(Len);
147  FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
148  if (I != FileEdits.begin())
149  --I;
150 
151  for (; I != FileEdits.end(); ++I) {
152  FileEdit &FA = I->second;
153  FileOffset B = I->first;
154  FileOffset E = B.getWithOffset(FA.RemoveLen);
155 
156  if (BeginOffs < E)
157  break;
158  }
159 
160  FileOffset TopBegin, TopEnd;
161  FileEdit *TopFA = nullptr;
162 
163  if (I == FileEdits.end()) {
164  FileEditsTy::iterator
165  NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
166  NewI->second.RemoveLen = Len;
167  return;
168  }
169 
170  FileEdit &FA = I->second;
171  FileOffset B = I->first;
172  FileOffset E = B.getWithOffset(FA.RemoveLen);
173  if (BeginOffs < B) {
174  FileEditsTy::iterator
175  NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
176  TopBegin = BeginOffs;
177  TopEnd = EndOffs;
178  TopFA = &NewI->second;
179  TopFA->RemoveLen = Len;
180  } else {
181  TopBegin = B;
182  TopEnd = E;
183  TopFA = &I->second;
184  if (TopEnd >= EndOffs)
185  return;
186  unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
187  TopEnd = EndOffs;
188  TopFA->RemoveLen += diff;
189  if (B == BeginOffs)
190  TopFA->Text = StringRef();
191  ++I;
192  }
193 
194  while (I != FileEdits.end()) {
195  FileEdit &FA = I->second;
196  FileOffset B = I->first;
197  FileOffset E = B.getWithOffset(FA.RemoveLen);
198 
199  if (B >= TopEnd)
200  break;
201 
202  if (E <= TopEnd) {
203  FileEdits.erase(I++);
204  continue;
205  }
206 
207  if (B < TopEnd) {
208  unsigned diff = E.getOffset() - TopEnd.getOffset();
209  TopEnd = E;
210  TopFA->RemoveLen += diff;
211  FileEdits.erase(I);
212  }
213 
214  break;
215  }
216 }
217 
218 bool EditedSource::commit(const Commit &commit) {
219  if (!commit.isCommitable())
220  return false;
221 
223  I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
224  const edit::Commit::Edit &edit = *I;
225  switch (edit.Kind) {
227  commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
228  break;
230  commitInsertFromRange(edit.OrigLoc, edit.Offset,
231  edit.InsertFromRangeOffs, edit.Length,
232  edit.BeforePrev);
233  break;
235  commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
236  break;
237  }
238  }
239 
240  return true;
241 }
242 
243 // \brief Returns true if it is ok to make the two given characters adjacent.
244 static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
245  // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
246  // making two '<' adjacent.
247  return !(Lexer::isIdentifierBodyChar(left, LangOpts) &&
248  Lexer::isIdentifierBodyChar(right, LangOpts));
249 }
250 
251 /// \brief Returns true if it is ok to eliminate the trailing whitespace between
252 /// the given characters.
253 static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
254  const LangOptions &LangOpts) {
255  if (!canBeJoined(left, right, LangOpts))
256  return false;
257  if (isWhitespace(left) || isWhitespace(right))
258  return true;
259  if (canBeJoined(beforeWSpace, right, LangOpts))
260  return false; // the whitespace was intentional, keep it.
261  return true;
262 }
263 
264 /// \brief Check the range that we are going to remove and:
265 /// -Remove any trailing whitespace if possible.
266 /// -Insert a space if removing the range is going to mess up the source tokens.
267 static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
268  SourceLocation Loc, FileOffset offs,
269  unsigned &len, StringRef &text) {
270  assert(len && text.empty());
271  SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
272  if (BeginTokLoc != Loc)
273  return; // the range is not at the beginning of a token, keep the range.
274 
275  bool Invalid = false;
276  StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
277  if (Invalid)
278  return;
279 
280  unsigned begin = offs.getOffset();
281  unsigned end = begin + len;
282 
283  // Do not try to extend the removal if we're at the end of the buffer already.
284  if (end == buffer.size())
285  return;
286 
287  assert(begin < buffer.size() && end < buffer.size() && "Invalid range!");
288 
289  // FIXME: Remove newline.
290 
291  if (begin == 0) {
292  if (buffer[end] == ' ')
293  ++len;
294  return;
295  }
296 
297  if (buffer[end] == ' ') {
298  assert((end + 1 != buffer.size() || buffer.data()[end + 1] == 0) &&
299  "buffer not zero-terminated!");
300  if (canRemoveWhitespace(/*left=*/buffer[begin-1],
301  /*beforeWSpace=*/buffer[end-1],
302  /*right=*/buffer.data()[end + 1], // zero-terminated
303  LangOpts))
304  ++len;
305  return;
306  }
307 
308  if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
309  text = " ";
310 }
311 
312 static void applyRewrite(EditsReceiver &receiver,
313  StringRef text, FileOffset offs, unsigned len,
314  const SourceManager &SM, const LangOptions &LangOpts) {
315  assert(!offs.getFID().isInvalid());
316  SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
317  Loc = Loc.getLocWithOffset(offs.getOffset());
318  assert(Loc.isFileID());
319 
320  if (text.empty())
321  adjustRemoval(SM, LangOpts, Loc, offs, len, text);
322 
324  Loc.getLocWithOffset(len));
325 
326  if (text.empty()) {
327  assert(len);
328  receiver.remove(range);
329  return;
330  }
331 
332  if (len)
333  receiver.replace(range, text);
334  else
335  receiver.insert(Loc, text);
336 }
337 
339  SmallString<128> StrVec;
340  FileOffset CurOffs, CurEnd;
341  unsigned CurLen;
342 
343  if (FileEdits.empty())
344  return;
345 
346  FileEditsTy::iterator I = FileEdits.begin();
347  CurOffs = I->first;
348  StrVec = I->second.Text;
349  CurLen = I->second.RemoveLen;
350  CurEnd = CurOffs.getWithOffset(CurLen);
351  ++I;
352 
353  for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
354  FileOffset offs = I->first;
355  FileEdit act = I->second;
356  assert(offs >= CurEnd);
357 
358  if (offs == CurEnd) {
359  StrVec += act.Text;
360  CurLen += act.RemoveLen;
361  CurEnd.getWithOffset(act.RemoveLen);
362  continue;
363  }
364 
365  applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts);
366  CurOffs = offs;
367  StrVec = act.Text;
368  CurLen = act.RemoveLen;
369  CurEnd = CurOffs.getWithOffset(CurLen);
370  }
371 
372  applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts);
373 }
374 
376  FileEdits.clear();
377  StrAlloc.Reset();
378 }
379 
380 StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
381  bool &Invalid) {
382  assert(BeginOffs.getFID() == EndOffs.getFID());
383  assert(BeginOffs <= EndOffs);
384  SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
385  BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
386  assert(BLoc.isFileID());
388  ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
390  SourceMgr, LangOpts, &Invalid);
391 }
392 
393 EditedSource::FileEditsTy::iterator
394 EditedSource::getActionForOffset(FileOffset Offs) {
395  FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
396  if (I == FileEdits.begin())
397  return FileEdits.end();
398  --I;
399  FileEdit &FA = I->second;
400  FileOffset B = I->first;
401  FileOffset E = B.getWithOffset(FA.RemoveLen);
402  if (Offs >= B && Offs < E)
403  return I;
404 
405  return FileEdits.end();
406 }
static LLVM_READONLY bool isWhitespace(unsigned char c)
Definition: CharInfo.h:88
Defines the SourceManager interface.
unsigned getRawEncoding() const
When a SourceLocation itself cannot be used, this returns an (opaque) 32-bit integer encoding for it...
void applyRewrites(EditsReceiver &receiver)
static void applyRewrite(EditsReceiver &receiver, StringRef text, FileOffset offs, unsigned len, const SourceManager &SM, const LangOptions &LangOpts)
unsigned getOffset() const
Definition: FileOffset.h:29
FileOffset getWithOffset(unsigned offset) const
Definition: FileOffset.h:31
StringRef getBufferData(FileID FID, bool *Invalid=nullptr) const
Return a StringRef to the source buffer data for the specified FileID.
bool isCommitable() const
Definition: Commit.h:65
bool isFileID() const
Keeps track of the various options that can be enabled, which controls the dialect of C or C++ that i...
Definition: LangOptions.h:48
SourceLocation getLocWithOffset(int Offset) const
Return a source location with the specified offset from this SourceLocation.
SourceLocation OrigLoc
Definition: Commit.h:36
edit_iterator edit_begin() const
Definition: Commit.h:110
SourceManager & SM
static StringRef getSourceText(CharSourceRange Range, const SourceManager &SM, const LangOptions &LangOpts, bool *Invalid=nullptr)
Returns a string for the source that the range encompasses.
Definition: Lexer.cpp:920
Represents a character-granular source range.
virtual void replace(CharSourceRange range, StringRef text)=0
FileID getFID() const
Definition: FileOffset.h:28
FileOffset InsertFromRangeOffs
Definition: Commit.h:38
virtual void remove(CharSourceRange range)
By default it calls replace with an empty string.
edit_iterator edit_end() const
Definition: Commit.h:111
static CharSourceRange getCharRange(SourceRange R)
Encodes a location in the source. The SourceManager can decode this to get at the full include stack...
static bool canBeJoined(char left, char right, const LangOptions &LangOpts)
SmallVectorImpl< Edit >::const_iterator edit_iterator
Definition: Commit.h:109
static SourceLocation GetBeginningOfToken(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts)
Given a location any where in a source buffer, find the location that corresponds to the beginning of...
Definition: Lexer.cpp:509
StringRef copyString(StringRef str)
Definition: EditedSource.h:65
std::pair< SourceLocation, SourceLocation > getImmediateExpansionRange(SourceLocation Loc) const
Return the start/end of the expansion information for an expansion location.
bool isInvalid() const
bool commit(const Commit &commit)
static bool isIdentifierBodyChar(char c, const LangOptions &LangOpts)
Returns true if the given character could appear in an identifier.
Definition: Lexer.cpp:1003
bool canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs)
static bool canRemoveWhitespace(char left, char beforeWSpace, char right, const LangOptions &LangOpts)
Returns true if it is ok to eliminate the trailing whitespace between the given characters.
virtual void insert(SourceLocation loc, StringRef text)=0
static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation Loc, FileOffset offs, unsigned &len, StringRef &text)
Check the range that we are going to remove and: -Remove any trailing whitespace if possible...
SourceLocation getLocForStartOfFile(FileID FID) const
Return the source location corresponding to the first byte of the specified file. ...
This class handles loading and caching of source files into memory.
bool isMacroArgExpansion(SourceLocation Loc) const
Tests whether the given source location represents a macro argument's expansion into the function-lik...