blob: 4b7af242fe99dba2c6386a4f3304aa8bb4ae3869 [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"
NAKAMURA Takumif50b8f02012-12-21 00:21:02 +000017#include <cctype>
Ted Kremenek30660a82012-03-06 20:06:33 +000018
19using namespace clang;
20using namespace edit;
21
22void EditsReceiver::remove(CharSourceRange range) {
23 replace(range, StringRef());
24}
25
26StringRef EditedSource::copyString(const Twine &twine) {
27 llvm::SmallString<128> Data;
28 return copyString(twine.toStringRef(Data));
29}
30
31bool 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
53bool 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 Twine concat;
76 if (beforePreviousInsertions)
77 concat = Twine(text) + FA.Text;
78 else
79 concat = Twine(FA.Text) + text;
80
81 FA.Text = copyString(concat);
82 return true;
83}
84
85bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
86 FileOffset Offs,
87 FileOffset InsertFromRangeOffs, unsigned Len,
88 bool beforePreviousInsertions) {
89 if (Len == 0)
90 return true;
91
92 llvm::SmallString<128> StrVec;
93 FileOffset BeginOffs = InsertFromRangeOffs;
94 FileOffset EndOffs = BeginOffs.getWithOffset(Len);
95 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
96 if (I != FileEdits.begin())
97 --I;
98
99 for (; I != FileEdits.end(); ++I) {
100 FileEdit &FA = I->second;
101 FileOffset B = I->first;
102 FileOffset E = B.getWithOffset(FA.RemoveLen);
103
Argyrios Kyrtzidis055b3952012-05-14 22:01:53 +0000104 if (BeginOffs == B)
105 break;
106
Ted Kremenek30660a82012-03-06 20:06:33 +0000107 if (BeginOffs < E) {
Argyrios Kyrtzidis055b3952012-05-14 22:01:53 +0000108 if (BeginOffs > B) {
Ted Kremenek30660a82012-03-06 20:06:33 +0000109 BeginOffs = E;
110 ++I;
111 }
112 break;
113 }
114 }
115
116 for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
117 FileEdit &FA = I->second;
118 FileOffset B = I->first;
119 FileOffset E = B.getWithOffset(FA.RemoveLen);
120
121 if (BeginOffs < B) {
122 bool Invalid = false;
123 StringRef text = getSourceText(BeginOffs, B, Invalid);
124 if (Invalid)
125 return false;
126 StrVec += text;
127 }
128 StrVec += FA.Text;
129 BeginOffs = E;
130 }
131
132 if (BeginOffs < EndOffs) {
133 bool Invalid = false;
134 StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
135 if (Invalid)
136 return false;
137 StrVec += text;
138 }
139
140 return commitInsert(OrigLoc, Offs, StrVec.str(), beforePreviousInsertions);
141}
142
143void EditedSource::commitRemove(SourceLocation OrigLoc,
144 FileOffset BeginOffs, unsigned Len) {
145 if (Len == 0)
146 return;
147
148 FileOffset EndOffs = BeginOffs.getWithOffset(Len);
149 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
150 if (I != FileEdits.begin())
151 --I;
152
153 for (; I != FileEdits.end(); ++I) {
154 FileEdit &FA = I->second;
155 FileOffset B = I->first;
156 FileOffset E = B.getWithOffset(FA.RemoveLen);
157
158 if (BeginOffs < E)
159 break;
160 }
161
162 FileOffset TopBegin, TopEnd;
163 FileEdit *TopFA = 0;
164
165 if (I == FileEdits.end()) {
166 FileEditsTy::iterator
167 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
168 NewI->second.RemoveLen = Len;
169 return;
170 }
171
172 FileEdit &FA = I->second;
173 FileOffset B = I->first;
174 FileOffset E = B.getWithOffset(FA.RemoveLen);
175 if (BeginOffs < B) {
176 FileEditsTy::iterator
177 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
178 TopBegin = BeginOffs;
179 TopEnd = EndOffs;
180 TopFA = &NewI->second;
181 TopFA->RemoveLen = Len;
182 } else {
183 TopBegin = B;
184 TopEnd = E;
185 TopFA = &I->second;
186 if (TopEnd >= EndOffs)
187 return;
188 unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
189 TopEnd = EndOffs;
190 TopFA->RemoveLen += diff;
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
218bool 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
Argyrios Kyrtzidis5964df12012-12-20 21:05:53 +0000243static inline bool isIdentifierChar(char c, const LangOptions &LangOpts) {
244 return std::isalnum(c) || c == '_' || (c == '$' && LangOpts.DollarIdents);
245}
246
247// \brief Returns true if it is ok to make the two given characters adjacent.
248static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
249 // FIXME: Should use the Lexer to make sure we don't allow stuff like
250 // making two '<' adjacent.
251 return !(isIdentifierChar(left, LangOpts) &&
252 isIdentifierChar(right, LangOpts));
253}
254
255/// \brief Returns true if it is ok to eliminate the trailing whitespace between
256/// the given characters.
257static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
258 const LangOptions &LangOpts) {
259 if (!canBeJoined(left, right, LangOpts))
260 return false;
261 if (std::isspace(left) || std::isspace(right))
262 return true;
263 if (canBeJoined(beforeWSpace, right, LangOpts))
264 return false; // the whitespace was intentional, keep it.
265 return true;
266}
267
268/// \brief Check the range that we are going to remove and:
269/// -Remove any trailing whitespace if possible.
270/// -Insert a space if removing the range is going to mess up the source tokens.
271static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
272 SourceLocation Loc, FileOffset offs,
273 unsigned &len, StringRef &text) {
274 assert(len && text.empty());
275 SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
276 if (BeginTokLoc != Loc)
277 return; // the range is not at the beginning of a token, keep the range.
278
279 bool Invalid = false;
280 StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
281 if (Invalid)
282 return;
283
284 unsigned begin = offs.getOffset();
285 unsigned end = begin + len;
286
287 // FIXME: Remove newline.
288
289 if (begin == 0) {
290 if (buffer[end] == ' ')
291 ++len;
292 return;
293 }
294
295 if (buffer[end] == ' ') {
296 if (canRemoveWhitespace(/*left=*/buffer[begin-1],
297 /*beforeWSpace=*/buffer[end-1],
298 /*right=*/buffer[end+1],
299 LangOpts))
300 ++len;
301 return;
302 }
303
304 if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
305 text = " ";
306}
307
Ted Kremenek30660a82012-03-06 20:06:33 +0000308static void applyRewrite(EditsReceiver &receiver,
309 StringRef text, FileOffset offs, unsigned len,
Argyrios Kyrtzidis5964df12012-12-20 21:05:53 +0000310 const SourceManager &SM, const LangOptions &LangOpts) {
Ted Kremenek30660a82012-03-06 20:06:33 +0000311 assert(!offs.getFID().isInvalid());
312 SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
313 Loc = Loc.getLocWithOffset(offs.getOffset());
314 assert(Loc.isFileID());
Argyrios Kyrtzidis5964df12012-12-20 21:05:53 +0000315
316 if (text.empty())
317 adjustRemoval(SM, LangOpts, Loc, offs, len, text);
318
Ted Kremenek30660a82012-03-06 20:06:33 +0000319 CharSourceRange range = CharSourceRange::getCharRange(Loc,
320 Loc.getLocWithOffset(len));
321
322 if (text.empty()) {
323 assert(len);
324 receiver.remove(range);
325 return;
326 }
327
328 if (len)
329 receiver.replace(range, text);
330 else
331 receiver.insert(Loc, text);
332}
333
334void EditedSource::applyRewrites(EditsReceiver &receiver) {
335 llvm::SmallString<128> StrVec;
336 FileOffset CurOffs, CurEnd;
337 unsigned CurLen;
338
339 if (FileEdits.empty())
340 return;
341
342 FileEditsTy::iterator I = FileEdits.begin();
343 CurOffs = I->first;
344 StrVec = I->second.Text;
345 CurLen = I->second.RemoveLen;
346 CurEnd = CurOffs.getWithOffset(CurLen);
347 ++I;
348
349 for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
350 FileOffset offs = I->first;
351 FileEdit act = I->second;
352 assert(offs >= CurEnd);
353
354 if (offs == CurEnd) {
355 StrVec += act.Text;
356 CurLen += act.RemoveLen;
357 CurEnd.getWithOffset(act.RemoveLen);
358 continue;
359 }
360
Argyrios Kyrtzidis5964df12012-12-20 21:05:53 +0000361 applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
Ted Kremenek30660a82012-03-06 20:06:33 +0000362 CurOffs = offs;
363 StrVec = act.Text;
364 CurLen = act.RemoveLen;
365 CurEnd = CurOffs.getWithOffset(CurLen);
366 }
367
Argyrios Kyrtzidis5964df12012-12-20 21:05:53 +0000368 applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
Ted Kremenek30660a82012-03-06 20:06:33 +0000369}
370
371void EditedSource::clearRewrites() {
372 FileEdits.clear();
373 StrAlloc.Reset();
374}
375
376StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
377 bool &Invalid) {
378 assert(BeginOffs.getFID() == EndOffs.getFID());
379 assert(BeginOffs <= EndOffs);
380 SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
381 BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
382 assert(BLoc.isFileID());
383 SourceLocation
384 ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
385 return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
386 SourceMgr, LangOpts, &Invalid);
387}
388
389EditedSource::FileEditsTy::iterator
390EditedSource::getActionForOffset(FileOffset Offs) {
391 FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
392 if (I == FileEdits.begin())
393 return FileEdits.end();
394 --I;
395 FileEdit &FA = I->second;
396 FileOffset B = I->first;
397 FileOffset E = B.getWithOffset(FA.RemoveLen);
398 if (Offs >= B && Offs < E)
399 return I;
400
401 return FileEdits.end();
402}