blob: 139a01fea8e552c47317852b20c11bb689ad0c60 [file] [log] [blame]
Ted Kremenek30660a82012-03-06 20:06:33 +00001//===----- 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"
Chandler Carruth55fc8732012-12-04 09:13:33 +000011#include "clang/Basic/SourceManager.h"
Ted Kremenek30660a82012-03-06 20:06:33 +000012#include "clang/Edit/Commit.h"
13#include "clang/Edit/EditsReceiver.h"
14#include "clang/Lex/Lexer.h"
Ted Kremenek30660a82012-03-06 20:06:33 +000015#include "llvm/ADT/SmallString.h"
16#include "llvm/ADT/Twine.h"
17
18using namespace clang;
19using namespace edit;
20
21void EditsReceiver::remove(CharSourceRange range) {
22 replace(range, StringRef());
23}
24
25StringRef EditedSource::copyString(const Twine &twine) {
26 llvm::SmallString<128> Data;
27 return copyString(twine.toStringRef(Data));
28}
29
30bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
31 FileEditsTy::iterator FA = getActionForOffset(Offs);
32 if (FA != FileEdits.end()) {
33 if (FA->first != Offs)
34 return false; // position has been removed.
35 }
36
37 if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
38 SourceLocation
39 DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
40 SourceLocation
41 ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
42 llvm::DenseMap<unsigned, SourceLocation>::iterator
43 I = ExpansionToArgMap.find(ExpLoc.getRawEncoding());
44 if (I != ExpansionToArgMap.end() && I->second != DefArgLoc)
45 return false; // Trying to write in a macro argument input that has
46 // already been written for another argument of the same macro.
47 }
48
49 return true;
50}
51
52bool EditedSource::commitInsert(SourceLocation OrigLoc,
53 FileOffset Offs, StringRef text,
54 bool beforePreviousInsertions) {
55 if (!canInsertInOffset(OrigLoc, Offs))
56 return false;
57 if (text.empty())
58 return true;
59
60 if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
61 SourceLocation
62 DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
63 SourceLocation
64 ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
65 ExpansionToArgMap[ExpLoc.getRawEncoding()] = DefArgLoc;
66 }
67
68 FileEdit &FA = FileEdits[Offs];
69 if (FA.Text.empty()) {
70 FA.Text = copyString(text);
71 return true;
72 }
73
74 Twine concat;
75 if (beforePreviousInsertions)
76 concat = Twine(text) + FA.Text;
77 else
78 concat = Twine(FA.Text) + text;
79
80 FA.Text = copyString(concat);
81 return true;
82}
83
84bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
85 FileOffset Offs,
86 FileOffset InsertFromRangeOffs, unsigned Len,
87 bool beforePreviousInsertions) {
88 if (Len == 0)
89 return true;
90
91 llvm::SmallString<128> StrVec;
92 FileOffset BeginOffs = InsertFromRangeOffs;
93 FileOffset EndOffs = BeginOffs.getWithOffset(Len);
94 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
95 if (I != FileEdits.begin())
96 --I;
97
98 for (; I != FileEdits.end(); ++I) {
99 FileEdit &FA = I->second;
100 FileOffset B = I->first;
101 FileOffset E = B.getWithOffset(FA.RemoveLen);
102
Argyrios Kyrtzidis055b3952012-05-14 22:01:53 +0000103 if (BeginOffs == B)
104 break;
105
Ted Kremenek30660a82012-03-06 20:06:33 +0000106 if (BeginOffs < E) {
Argyrios Kyrtzidis055b3952012-05-14 22:01:53 +0000107 if (BeginOffs > B) {
Ted Kremenek30660a82012-03-06 20:06:33 +0000108 BeginOffs = E;
109 ++I;
110 }
111 break;
112 }
113 }
114
115 for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
116 FileEdit &FA = I->second;
117 FileOffset B = I->first;
118 FileOffset E = B.getWithOffset(FA.RemoveLen);
119
120 if (BeginOffs < B) {
121 bool Invalid = false;
122 StringRef text = getSourceText(BeginOffs, B, Invalid);
123 if (Invalid)
124 return false;
125 StrVec += text;
126 }
127 StrVec += FA.Text;
128 BeginOffs = E;
129 }
130
131 if (BeginOffs < EndOffs) {
132 bool Invalid = false;
133 StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
134 if (Invalid)
135 return false;
136 StrVec += text;
137 }
138
139 return commitInsert(OrigLoc, Offs, StrVec.str(), beforePreviousInsertions);
140}
141
142void EditedSource::commitRemove(SourceLocation OrigLoc,
143 FileOffset BeginOffs, unsigned Len) {
144 if (Len == 0)
145 return;
146
147 FileOffset EndOffs = BeginOffs.getWithOffset(Len);
148 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
149 if (I != FileEdits.begin())
150 --I;
151
152 for (; I != FileEdits.end(); ++I) {
153 FileEdit &FA = I->second;
154 FileOffset B = I->first;
155 FileOffset E = B.getWithOffset(FA.RemoveLen);
156
157 if (BeginOffs < E)
158 break;
159 }
160
161 FileOffset TopBegin, TopEnd;
162 FileEdit *TopFA = 0;
163
164 if (I == FileEdits.end()) {
165 FileEditsTy::iterator
166 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
167 NewI->second.RemoveLen = Len;
168 return;
169 }
170
171 FileEdit &FA = I->second;
172 FileOffset B = I->first;
173 FileOffset E = B.getWithOffset(FA.RemoveLen);
174 if (BeginOffs < B) {
175 FileEditsTy::iterator
176 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
177 TopBegin = BeginOffs;
178 TopEnd = EndOffs;
179 TopFA = &NewI->second;
180 TopFA->RemoveLen = Len;
181 } else {
182 TopBegin = B;
183 TopEnd = E;
184 TopFA = &I->second;
185 if (TopEnd >= EndOffs)
186 return;
187 unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
188 TopEnd = EndOffs;
189 TopFA->RemoveLen += diff;
190 ++I;
191 }
192
193 while (I != FileEdits.end()) {
194 FileEdit &FA = I->second;
195 FileOffset B = I->first;
196 FileOffset E = B.getWithOffset(FA.RemoveLen);
197
198 if (B >= TopEnd)
199 break;
200
201 if (E <= TopEnd) {
202 FileEdits.erase(I++);
203 continue;
204 }
205
206 if (B < TopEnd) {
207 unsigned diff = E.getOffset() - TopEnd.getOffset();
208 TopEnd = E;
209 TopFA->RemoveLen += diff;
210 FileEdits.erase(I);
211 }
212
213 break;
214 }
215}
216
217bool EditedSource::commit(const Commit &commit) {
218 if (!commit.isCommitable())
219 return false;
220
221 for (edit::Commit::edit_iterator
222 I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
223 const edit::Commit::Edit &edit = *I;
224 switch (edit.Kind) {
225 case edit::Commit::Act_Insert:
226 commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
227 break;
228 case edit::Commit::Act_InsertFromRange:
229 commitInsertFromRange(edit.OrigLoc, edit.Offset,
230 edit.InsertFromRangeOffs, edit.Length,
231 edit.BeforePrev);
232 break;
233 case edit::Commit::Act_Remove:
234 commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
235 break;
236 }
237 }
238
239 return true;
240}
241
Argyrios Kyrtzidis5964df12012-12-20 21:05:53 +0000242static inline bool isIdentifierChar(char c, const LangOptions &LangOpts) {
243 return std::isalnum(c) || c == '_' || (c == '$' && LangOpts.DollarIdents);
244}
245
246// \brief Returns true if it is ok to make the two given characters adjacent.
247static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
248 // FIXME: Should use the Lexer to make sure we don't allow stuff like
249 // making two '<' adjacent.
250 return !(isIdentifierChar(left, LangOpts) &&
251 isIdentifierChar(right, LangOpts));
252}
253
254/// \brief Returns true if it is ok to eliminate the trailing whitespace between
255/// the given characters.
256static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
257 const LangOptions &LangOpts) {
258 if (!canBeJoined(left, right, LangOpts))
259 return false;
260 if (std::isspace(left) || std::isspace(right))
261 return true;
262 if (canBeJoined(beforeWSpace, right, LangOpts))
263 return false; // the whitespace was intentional, keep it.
264 return true;
265}
266
267/// \brief Check the range that we are going to remove and:
268/// -Remove any trailing whitespace if possible.
269/// -Insert a space if removing the range is going to mess up the source tokens.
270static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
271 SourceLocation Loc, FileOffset offs,
272 unsigned &len, StringRef &text) {
273 assert(len && text.empty());
274 SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
275 if (BeginTokLoc != Loc)
276 return; // the range is not at the beginning of a token, keep the range.
277
278 bool Invalid = false;
279 StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
280 if (Invalid)
281 return;
282
283 unsigned begin = offs.getOffset();
284 unsigned end = begin + len;
285
286 // FIXME: Remove newline.
287
288 if (begin == 0) {
289 if (buffer[end] == ' ')
290 ++len;
291 return;
292 }
293
294 if (buffer[end] == ' ') {
295 if (canRemoveWhitespace(/*left=*/buffer[begin-1],
296 /*beforeWSpace=*/buffer[end-1],
297 /*right=*/buffer[end+1],
298 LangOpts))
299 ++len;
300 return;
301 }
302
303 if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
304 text = " ";
305}
306
Ted Kremenek30660a82012-03-06 20:06:33 +0000307static void applyRewrite(EditsReceiver &receiver,
308 StringRef text, FileOffset offs, unsigned len,
Argyrios Kyrtzidis5964df12012-12-20 21:05:53 +0000309 const SourceManager &SM, const LangOptions &LangOpts) {
Ted Kremenek30660a82012-03-06 20:06:33 +0000310 assert(!offs.getFID().isInvalid());
311 SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
312 Loc = Loc.getLocWithOffset(offs.getOffset());
313 assert(Loc.isFileID());
Argyrios Kyrtzidis5964df12012-12-20 21:05:53 +0000314
315 if (text.empty())
316 adjustRemoval(SM, LangOpts, Loc, offs, len, text);
317
Ted Kremenek30660a82012-03-06 20:06:33 +0000318 CharSourceRange range = CharSourceRange::getCharRange(Loc,
319 Loc.getLocWithOffset(len));
320
321 if (text.empty()) {
322 assert(len);
323 receiver.remove(range);
324 return;
325 }
326
327 if (len)
328 receiver.replace(range, text);
329 else
330 receiver.insert(Loc, text);
331}
332
333void EditedSource::applyRewrites(EditsReceiver &receiver) {
334 llvm::SmallString<128> StrVec;
335 FileOffset CurOffs, CurEnd;
336 unsigned CurLen;
337
338 if (FileEdits.empty())
339 return;
340
341 FileEditsTy::iterator I = FileEdits.begin();
342 CurOffs = I->first;
343 StrVec = I->second.Text;
344 CurLen = I->second.RemoveLen;
345 CurEnd = CurOffs.getWithOffset(CurLen);
346 ++I;
347
348 for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
349 FileOffset offs = I->first;
350 FileEdit act = I->second;
351 assert(offs >= CurEnd);
352
353 if (offs == CurEnd) {
354 StrVec += act.Text;
355 CurLen += act.RemoveLen;
356 CurEnd.getWithOffset(act.RemoveLen);
357 continue;
358 }
359
Argyrios Kyrtzidis5964df12012-12-20 21:05:53 +0000360 applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
Ted Kremenek30660a82012-03-06 20:06:33 +0000361 CurOffs = offs;
362 StrVec = act.Text;
363 CurLen = act.RemoveLen;
364 CurEnd = CurOffs.getWithOffset(CurLen);
365 }
366
Argyrios Kyrtzidis5964df12012-12-20 21:05:53 +0000367 applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
Ted Kremenek30660a82012-03-06 20:06:33 +0000368}
369
370void EditedSource::clearRewrites() {
371 FileEdits.clear();
372 StrAlloc.Reset();
373}
374
375StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
376 bool &Invalid) {
377 assert(BeginOffs.getFID() == EndOffs.getFID());
378 assert(BeginOffs <= EndOffs);
379 SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
380 BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
381 assert(BLoc.isFileID());
382 SourceLocation
383 ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
384 return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
385 SourceMgr, LangOpts, &Invalid);
386}
387
388EditedSource::FileEditsTy::iterator
389EditedSource::getActionForOffset(FileOffset Offs) {
390 FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
391 if (I == FileEdits.begin())
392 return FileEdits.end();
393 --I;
394 FileEdit &FA = I->second;
395 FileOffset B = I->first;
396 FileOffset E = B.getWithOffset(FA.RemoveLen);
397 if (Offs >= B && Offs < E)
398 return I;
399
400 return FileEdits.end();
401}