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
10 #include "clang/Edit/EditedSource.h"
11 #include "clang/Basic/CharInfo.h"
12 #include "clang/Basic/SourceManager.h"
13 #include "clang/Edit/Commit.h"
14 #include "clang/Edit/EditsReceiver.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
remove(CharSourceRange range)22 void EditsReceiver::remove(CharSourceRange range) {
23 replace(range, StringRef());
24 }
25
copyString(const Twine & twine)26 StringRef EditedSource::copyString(const Twine &twine) {
27 SmallString<128> Data;
28 return copyString(twine.toStringRef(Data));
29 }
30
canInsertInOffset(SourceLocation OrigLoc,FileOffset Offs)31 bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
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)) {
39 SourceLocation
40 DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
41 SourceLocation
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
commitInsert(SourceLocation OrigLoc,FileOffset Offs,StringRef text,bool beforePreviousInsertions)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)) {
62 SourceLocation
63 DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
64 SourceLocation
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
commitInsertFromRange(SourceLocation OrigLoc,FileOffset Offs,FileOffset InsertFromRangeOffs,unsigned Len,bool beforePreviousInsertions)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
commitRemove(SourceLocation OrigLoc,FileOffset BeginOffs,unsigned Len)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
commit(const Commit & commit)218 bool EditedSource::commit(const Commit &commit) {
219 if (!commit.isCommitable())
220 return false;
221
222 for (edit::Commit::edit_iterator
223 I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
224 const edit::Commit::Edit &edit = *I;
225 switch (edit.Kind) {
226 case edit::Commit::Act_Insert:
227 commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
228 break;
229 case edit::Commit::Act_InsertFromRange:
230 commitInsertFromRange(edit.OrigLoc, edit.Offset,
231 edit.InsertFromRangeOffs, edit.Length,
232 edit.BeforePrev);
233 break;
234 case edit::Commit::Act_Remove:
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.
canBeJoined(char left,char right,const LangOptions & LangOpts)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.
canRemoveWhitespace(char left,char beforeWSpace,char right,const LangOptions & LangOpts)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.
adjustRemoval(const SourceManager & SM,const LangOptions & LangOpts,SourceLocation Loc,FileOffset offs,unsigned & len,StringRef & text)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
applyRewrite(EditsReceiver & receiver,StringRef text,FileOffset offs,unsigned len,const SourceManager & SM,const LangOptions & LangOpts)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
323 CharSourceRange range = CharSourceRange::getCharRange(Loc,
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
applyRewrites(EditsReceiver & receiver)338 void EditedSource::applyRewrites(EditsReceiver &receiver) {
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
clearRewrites()375 void EditedSource::clearRewrites() {
376 FileEdits.clear();
377 StrAlloc.Reset();
378 }
379
getSourceText(FileOffset BeginOffs,FileOffset EndOffs,bool & Invalid)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());
387 SourceLocation
388 ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
389 return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
390 SourceMgr, LangOpts, &Invalid);
391 }
392
393 EditedSource::FileEditsTy::iterator
getActionForOffset(FileOffset Offs)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 }
407