clang API Documentation

EditedSource.cpp
Go to the documentation of this file.
00001 //===----- EditedSource.cpp - Collection of source edits ------------------===//
00002 //
00003 //                     The LLVM Compiler Infrastructure
00004 //
00005 // This file is distributed under the University of Illinois Open Source
00006 // License. See LICENSE.TXT for details.
00007 //
00008 //===----------------------------------------------------------------------===//
00009 
00010 #include "clang/Edit/EditedSource.h"
00011 #include "clang/Basic/CharInfo.h"
00012 #include "clang/Basic/SourceManager.h"
00013 #include "clang/Edit/Commit.h"
00014 #include "clang/Edit/EditsReceiver.h"
00015 #include "clang/Lex/Lexer.h"
00016 #include "llvm/ADT/SmallString.h"
00017 #include "llvm/ADT/Twine.h"
00018 
00019 using namespace clang;
00020 using namespace edit;
00021 
00022 void EditsReceiver::remove(CharSourceRange range) {
00023   replace(range, StringRef());
00024 }
00025 
00026 StringRef EditedSource::copyString(const Twine &twine) {
00027   SmallString<128> Data;
00028   return copyString(twine.toStringRef(Data));
00029 }
00030 
00031 bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
00032   FileEditsTy::iterator FA = getActionForOffset(Offs);
00033   if (FA != FileEdits.end()) {
00034     if (FA->first != Offs)
00035       return false; // position has been removed.
00036   }
00037 
00038   if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
00039     SourceLocation
00040       DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
00041     SourceLocation
00042       ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
00043     llvm::DenseMap<unsigned, SourceLocation>::iterator
00044       I = ExpansionToArgMap.find(ExpLoc.getRawEncoding());
00045     if (I != ExpansionToArgMap.end() && I->second != DefArgLoc)
00046       return false; // Trying to write in a macro argument input that has
00047                  // already been written for another argument of the same macro. 
00048   }
00049 
00050   return true;
00051 }
00052 
00053 bool EditedSource::commitInsert(SourceLocation OrigLoc,
00054                                 FileOffset Offs, StringRef text,
00055                                 bool beforePreviousInsertions) {
00056   if (!canInsertInOffset(OrigLoc, Offs))
00057     return false;
00058   if (text.empty())
00059     return true;
00060 
00061   if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
00062     SourceLocation
00063       DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
00064     SourceLocation
00065       ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
00066     ExpansionToArgMap[ExpLoc.getRawEncoding()] = DefArgLoc;
00067   }
00068   
00069   FileEdit &FA = FileEdits[Offs];
00070   if (FA.Text.empty()) {
00071     FA.Text = copyString(text);
00072     return true;
00073   }
00074 
00075   if (beforePreviousInsertions)
00076     FA.Text = copyString(Twine(text) + FA.Text);
00077   else
00078     FA.Text = copyString(Twine(FA.Text) + text);
00079 
00080   return true;
00081 }
00082 
00083 bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
00084                                    FileOffset Offs,
00085                                    FileOffset InsertFromRangeOffs, unsigned Len,
00086                                    bool beforePreviousInsertions) {
00087   if (Len == 0)
00088     return true;
00089 
00090   SmallString<128> StrVec;
00091   FileOffset BeginOffs = InsertFromRangeOffs;
00092   FileOffset EndOffs = BeginOffs.getWithOffset(Len);
00093   FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
00094   if (I != FileEdits.begin())
00095     --I;
00096 
00097   for (; I != FileEdits.end(); ++I) {
00098     FileEdit &FA = I->second;
00099     FileOffset B = I->first;
00100     FileOffset E = B.getWithOffset(FA.RemoveLen);
00101 
00102     if (BeginOffs == B)
00103       break;
00104 
00105     if (BeginOffs < E) {
00106       if (BeginOffs > B) {
00107         BeginOffs = E;
00108         ++I;
00109       }
00110       break;
00111     }
00112   }
00113 
00114   for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
00115     FileEdit &FA = I->second;
00116     FileOffset B = I->first;
00117     FileOffset E = B.getWithOffset(FA.RemoveLen);
00118 
00119     if (BeginOffs < B) {
00120       bool Invalid = false;
00121       StringRef text = getSourceText(BeginOffs, B, Invalid);
00122       if (Invalid)
00123         return false;
00124       StrVec += text;
00125     }
00126     StrVec += FA.Text;
00127     BeginOffs = E;
00128   }
00129 
00130   if (BeginOffs < EndOffs) {
00131     bool Invalid = false;
00132     StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
00133     if (Invalid)
00134       return false;
00135     StrVec += text;
00136   }
00137 
00138   return commitInsert(OrigLoc, Offs, StrVec.str(), beforePreviousInsertions);
00139 }
00140 
00141 void EditedSource::commitRemove(SourceLocation OrigLoc,
00142                                 FileOffset BeginOffs, unsigned Len) {
00143   if (Len == 0)
00144     return;
00145 
00146   FileOffset EndOffs = BeginOffs.getWithOffset(Len);
00147   FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
00148   if (I != FileEdits.begin())
00149     --I;
00150 
00151   for (; I != FileEdits.end(); ++I) {
00152     FileEdit &FA = I->second;
00153     FileOffset B = I->first;
00154     FileOffset E = B.getWithOffset(FA.RemoveLen);
00155 
00156     if (BeginOffs < E)
00157       break;
00158   }
00159 
00160   FileOffset TopBegin, TopEnd;
00161   FileEdit *TopFA = nullptr;
00162 
00163   if (I == FileEdits.end()) {
00164     FileEditsTy::iterator
00165       NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
00166     NewI->second.RemoveLen = Len;
00167     return;
00168   }
00169 
00170   FileEdit &FA = I->second;
00171   FileOffset B = I->first;
00172   FileOffset E = B.getWithOffset(FA.RemoveLen);
00173   if (BeginOffs < B) {
00174     FileEditsTy::iterator
00175       NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
00176     TopBegin = BeginOffs;
00177     TopEnd = EndOffs;
00178     TopFA = &NewI->second;
00179     TopFA->RemoveLen = Len;
00180   } else {
00181     TopBegin = B;
00182     TopEnd = E;
00183     TopFA = &I->second;
00184     if (TopEnd >= EndOffs)
00185       return;
00186     unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
00187     TopEnd = EndOffs;
00188     TopFA->RemoveLen += diff;
00189     if (B == BeginOffs)
00190       TopFA->Text = StringRef();
00191     ++I;
00192   }
00193 
00194   while (I != FileEdits.end()) {
00195     FileEdit &FA = I->second;
00196     FileOffset B = I->first;
00197     FileOffset E = B.getWithOffset(FA.RemoveLen);
00198 
00199     if (B >= TopEnd)
00200       break;
00201 
00202     if (E <= TopEnd) {
00203       FileEdits.erase(I++);
00204       continue;
00205     }
00206 
00207     if (B < TopEnd) {
00208       unsigned diff = E.getOffset() - TopEnd.getOffset();
00209       TopEnd = E;
00210       TopFA->RemoveLen += diff;
00211       FileEdits.erase(I);
00212     }
00213 
00214     break;
00215   }
00216 }
00217 
00218 bool EditedSource::commit(const Commit &commit) {
00219   if (!commit.isCommitable())
00220     return false;
00221 
00222   for (edit::Commit::edit_iterator
00223          I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
00224     const edit::Commit::Edit &edit = *I;
00225     switch (edit.Kind) {
00226     case edit::Commit::Act_Insert:
00227       commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
00228       break;
00229     case edit::Commit::Act_InsertFromRange:
00230       commitInsertFromRange(edit.OrigLoc, edit.Offset,
00231                             edit.InsertFromRangeOffs, edit.Length,
00232                             edit.BeforePrev);
00233       break;
00234     case edit::Commit::Act_Remove:
00235       commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
00236       break;
00237     }
00238   }
00239 
00240   return true;
00241 }
00242 
00243 // \brief Returns true if it is ok to make the two given characters adjacent.
00244 static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
00245   // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
00246   // making two '<' adjacent.
00247   return !(Lexer::isIdentifierBodyChar(left, LangOpts) &&
00248            Lexer::isIdentifierBodyChar(right, LangOpts));
00249 }
00250 
00251 /// \brief Returns true if it is ok to eliminate the trailing whitespace between
00252 /// the given characters.
00253 static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
00254                                 const LangOptions &LangOpts) {
00255   if (!canBeJoined(left, right, LangOpts))
00256     return false;
00257   if (isWhitespace(left) || isWhitespace(right))
00258     return true;
00259   if (canBeJoined(beforeWSpace, right, LangOpts))
00260     return false; // the whitespace was intentional, keep it.
00261   return true;
00262 }
00263 
00264 /// \brief Check the range that we are going to remove and:
00265 /// -Remove any trailing whitespace if possible.
00266 /// -Insert a space if removing the range is going to mess up the source tokens.
00267 static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
00268                           SourceLocation Loc, FileOffset offs,
00269                           unsigned &len, StringRef &text) {
00270   assert(len && text.empty());
00271   SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
00272   if (BeginTokLoc != Loc)
00273     return; // the range is not at the beginning of a token, keep the range.
00274 
00275   bool Invalid = false;
00276   StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
00277   if (Invalid)
00278     return;
00279 
00280   unsigned begin = offs.getOffset();
00281   unsigned end = begin + len;
00282 
00283   // Do not try to extend the removal if we're at the end of the buffer already.
00284   if (end == buffer.size())
00285     return;
00286 
00287   assert(begin < buffer.size() && end < buffer.size() && "Invalid range!");
00288 
00289   // FIXME: Remove newline.
00290 
00291   if (begin == 0) {
00292     if (buffer[end] == ' ')
00293       ++len;
00294     return;
00295   }
00296 
00297   if (buffer[end] == ' ') {
00298     if (canRemoveWhitespace(/*left=*/buffer[begin-1],
00299                             /*beforeWSpace=*/buffer[end-1],
00300                             /*right=*/buffer[end+1],
00301                             LangOpts))
00302       ++len;
00303     return;
00304   }
00305 
00306   if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
00307     text = " ";
00308 }
00309 
00310 static void applyRewrite(EditsReceiver &receiver,
00311                          StringRef text, FileOffset offs, unsigned len,
00312                          const SourceManager &SM, const LangOptions &LangOpts) {
00313   assert(!offs.getFID().isInvalid());
00314   SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
00315   Loc = Loc.getLocWithOffset(offs.getOffset());
00316   assert(Loc.isFileID());
00317 
00318   if (text.empty())
00319     adjustRemoval(SM, LangOpts, Loc, offs, len, text);
00320 
00321   CharSourceRange range = CharSourceRange::getCharRange(Loc,
00322                                                      Loc.getLocWithOffset(len));
00323 
00324   if (text.empty()) {
00325     assert(len);
00326     receiver.remove(range);
00327     return;
00328   }
00329 
00330   if (len)
00331     receiver.replace(range, text);
00332   else
00333     receiver.insert(Loc, text);
00334 }
00335 
00336 void EditedSource::applyRewrites(EditsReceiver &receiver) {
00337   SmallString<128> StrVec;
00338   FileOffset CurOffs, CurEnd;
00339   unsigned CurLen;
00340 
00341   if (FileEdits.empty())
00342     return;
00343 
00344   FileEditsTy::iterator I = FileEdits.begin();
00345   CurOffs = I->first;
00346   StrVec = I->second.Text;
00347   CurLen = I->second.RemoveLen;
00348   CurEnd = CurOffs.getWithOffset(CurLen);
00349   ++I;
00350 
00351   for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
00352     FileOffset offs = I->first;
00353     FileEdit act = I->second;
00354     assert(offs >= CurEnd);
00355 
00356     if (offs == CurEnd) {
00357       StrVec += act.Text;
00358       CurLen += act.RemoveLen;
00359       CurEnd.getWithOffset(act.RemoveLen);
00360       continue;
00361     }
00362 
00363     applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
00364     CurOffs = offs;
00365     StrVec = act.Text;
00366     CurLen = act.RemoveLen;
00367     CurEnd = CurOffs.getWithOffset(CurLen);
00368   }
00369 
00370   applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
00371 }
00372 
00373 void EditedSource::clearRewrites() {
00374   FileEdits.clear();
00375   StrAlloc.Reset();
00376 }
00377 
00378 StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
00379                                       bool &Invalid) {
00380   assert(BeginOffs.getFID() == EndOffs.getFID());
00381   assert(BeginOffs <= EndOffs);
00382   SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
00383   BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
00384   assert(BLoc.isFileID());
00385   SourceLocation
00386     ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
00387   return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
00388                               SourceMgr, LangOpts, &Invalid);
00389 }
00390 
00391 EditedSource::FileEditsTy::iterator
00392 EditedSource::getActionForOffset(FileOffset Offs) {
00393   FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
00394   if (I == FileEdits.begin())
00395     return FileEdits.end();
00396   --I;
00397   FileEdit &FA = I->second;
00398   FileOffset B = I->first;
00399   FileOffset E = B.getWithOffset(FA.RemoveLen);
00400   if (Offs >= B && Offs < E)
00401     return I;
00402 
00403   return FileEdits.end();
00404 }