1 //===----- EditedSource.cpp - Collection of source edits ------------------===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
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"
19 using namespace clang;
22 void EditsReceiver::remove(CharSourceRange range) {
23 replace(range, StringRef());
26 StringRef EditedSource::copyString(const Twine &twine) {
27 SmallString<128> Data;
28 return copyString(twine.toStringRef(Data));
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.
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.
53 bool EditedSource::commitInsert(SourceLocation OrigLoc,
54 FileOffset Offs, StringRef text,
55 bool beforePreviousInsertions) {
56 if (!canInsertInOffset(OrigLoc, Offs))
61 if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
63 DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
65 ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
66 ExpansionToArgMap[ExpLoc.getRawEncoding()] = DefArgLoc;
69 FileEdit &FA = FileEdits[Offs];
70 if (FA.Text.empty()) {
71 FA.Text = copyString(text);
76 if (beforePreviousInsertions)
77 concat = Twine(text) + FA.Text;
79 concat = Twine(FA.Text) + text;
81 FA.Text = copyString(concat);
85 bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
87 FileOffset InsertFromRangeOffs, unsigned Len,
88 bool beforePreviousInsertions) {
92 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())
99 for (; I != FileEdits.end(); ++I) {
100 FileEdit &FA = I->second;
101 FileOffset B = I->first;
102 FileOffset E = B.getWithOffset(FA.RemoveLen);
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);
122 bool Invalid = false;
123 StringRef text = getSourceText(BeginOffs, B, Invalid);
132 if (BeginOffs < EndOffs) {
133 bool Invalid = false;
134 StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
140 return commitInsert(OrigLoc, Offs, StrVec.str(), beforePreviousInsertions);
143 void EditedSource::commitRemove(SourceLocation OrigLoc,
144 FileOffset BeginOffs, unsigned Len) {
148 FileOffset EndOffs = BeginOffs.getWithOffset(Len);
149 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
150 if (I != FileEdits.begin())
153 for (; I != FileEdits.end(); ++I) {
154 FileEdit &FA = I->second;
155 FileOffset B = I->first;
156 FileOffset E = B.getWithOffset(FA.RemoveLen);
162 FileOffset TopBegin, TopEnd;
165 if (I == FileEdits.end()) {
166 FileEditsTy::iterator
167 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
168 NewI->second.RemoveLen = Len;
172 FileEdit &FA = I->second;
173 FileOffset B = I->first;
174 FileOffset E = B.getWithOffset(FA.RemoveLen);
176 FileEditsTy::iterator
177 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
178 TopBegin = BeginOffs;
180 TopFA = &NewI->second;
181 TopFA->RemoveLen = Len;
186 if (TopEnd >= EndOffs)
188 unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
190 TopFA->RemoveLen += diff;
192 TopFA->Text = StringRef();
196 while (I != FileEdits.end()) {
197 FileEdit &FA = I->second;
198 FileOffset B = I->first;
199 FileOffset E = B.getWithOffset(FA.RemoveLen);
205 FileEdits.erase(I++);
210 unsigned diff = E.getOffset() - TopEnd.getOffset();
212 TopFA->RemoveLen += diff;
220 bool EditedSource::commit(const Commit &commit) {
221 if (!commit.isCommitable())
224 for (edit::Commit::edit_iterator
225 I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
226 const edit::Commit::Edit &edit = *I;
228 case edit::Commit::Act_Insert:
229 commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
231 case edit::Commit::Act_InsertFromRange:
232 commitInsertFromRange(edit.OrigLoc, edit.Offset,
233 edit.InsertFromRangeOffs, edit.Length,
236 case edit::Commit::Act_Remove:
237 commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
245 // \brief Returns true if it is ok to make the two given characters adjacent.
246 static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
247 // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
248 // making two '<' adjacent.
249 return !(Lexer::isIdentifierBodyChar(left, LangOpts) &&
250 Lexer::isIdentifierBodyChar(right, LangOpts));
253 /// \brief Returns true if it is ok to eliminate the trailing whitespace between
254 /// the given characters.
255 static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
256 const LangOptions &LangOpts) {
257 if (!canBeJoined(left, right, LangOpts))
259 if (isWhitespace(left) || isWhitespace(right))
261 if (canBeJoined(beforeWSpace, right, LangOpts))
262 return false; // the whitespace was intentional, keep it.
266 /// \brief Check the range that we are going to remove and:
267 /// -Remove any trailing whitespace if possible.
268 /// -Insert a space if removing the range is going to mess up the source tokens.
269 static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
270 SourceLocation Loc, FileOffset offs,
271 unsigned &len, StringRef &text) {
272 assert(len && text.empty());
273 SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
274 if (BeginTokLoc != Loc)
275 return; // the range is not at the beginning of a token, keep the range.
277 bool Invalid = false;
278 StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
282 unsigned begin = offs.getOffset();
283 unsigned end = begin + len;
285 // FIXME: Remove newline.
288 if (buffer[end] == ' ')
293 if (buffer[end] == ' ') {
294 if (canRemoveWhitespace(/*left=*/buffer[begin-1],
295 /*beforeWSpace=*/buffer[end-1],
296 /*right=*/buffer[end+1],
302 if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
306 static void applyRewrite(EditsReceiver &receiver,
307 StringRef text, FileOffset offs, unsigned len,
308 const SourceManager &SM, const LangOptions &LangOpts) {
309 assert(!offs.getFID().isInvalid());
310 SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
311 Loc = Loc.getLocWithOffset(offs.getOffset());
312 assert(Loc.isFileID());
315 adjustRemoval(SM, LangOpts, Loc, offs, len, text);
317 CharSourceRange range = CharSourceRange::getCharRange(Loc,
318 Loc.getLocWithOffset(len));
322 receiver.remove(range);
327 receiver.replace(range, text);
329 receiver.insert(Loc, text);
332 void EditedSource::applyRewrites(EditsReceiver &receiver) {
333 SmallString<128> StrVec;
334 FileOffset CurOffs, CurEnd;
337 if (FileEdits.empty())
340 FileEditsTy::iterator I = FileEdits.begin();
342 StrVec = I->second.Text;
343 CurLen = I->second.RemoveLen;
344 CurEnd = CurOffs.getWithOffset(CurLen);
347 for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
348 FileOffset offs = I->first;
349 FileEdit act = I->second;
350 assert(offs >= CurEnd);
352 if (offs == CurEnd) {
354 CurLen += act.RemoveLen;
355 CurEnd.getWithOffset(act.RemoveLen);
359 applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
362 CurLen = act.RemoveLen;
363 CurEnd = CurOffs.getWithOffset(CurLen);
366 applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
369 void EditedSource::clearRewrites() {
374 StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
376 assert(BeginOffs.getFID() == EndOffs.getFID());
377 assert(BeginOffs <= EndOffs);
378 SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
379 BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
380 assert(BLoc.isFileID());
382 ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
383 return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
384 SourceMgr, LangOpts, &Invalid);
387 EditedSource::FileEditsTy::iterator
388 EditedSource::getActionForOffset(FileOffset Offs) {
389 FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
390 if (I == FileEdits.begin())
391 return FileEdits.end();
393 FileEdit &FA = I->second;
394 FileOffset B = I->first;
395 FileOffset E = B.getWithOffset(FA.RemoveLen);
396 if (Offs >= B && Offs < E)
399 return FileEdits.end();