Home | History | Annotate | Download | only in Edit
      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 
     22 void EditsReceiver::remove(CharSourceRange range) {
     23   replace(range, StringRef());
     24 }
     25 
     26 StringRef EditedSource::copyString(const Twine &twine) {
     27   SmallString<128> Data;
     28   return copyString(twine.toStringRef(Data));
     29 }
     30 
     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 
     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   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 
     85 bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
     86                                    FileOffset Offs,
     87                                    FileOffset InsertFromRangeOffs, unsigned Len,
     88                                    bool beforePreviousInsertions) {
     89   if (Len == 0)
     90     return true;
     91 
     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())
     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 
    104     if (BeginOffs == B)
    105       break;
    106 
    107     if (BeginOffs < E) {
    108       if (BeginOffs > B) {
    109         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 
    143 void 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     if (B == BeginOffs)
    192       TopFA->Text = StringRef();
    193     ++I;
    194   }
    195 
    196   while (I != FileEdits.end()) {
    197     FileEdit &FA = I->second;
    198     FileOffset B = I->first;
    199     FileOffset E = B.getWithOffset(FA.RemoveLen);
    200 
    201     if (B >= TopEnd)
    202       break;
    203 
    204     if (E <= TopEnd) {
    205       FileEdits.erase(I++);
    206       continue;
    207     }
    208 
    209     if (B < TopEnd) {
    210       unsigned diff = E.getOffset() - TopEnd.getOffset();
    211       TopEnd = E;
    212       TopFA->RemoveLen += diff;
    213       FileEdits.erase(I);
    214     }
    215 
    216     break;
    217   }
    218 }
    219 
    220 bool EditedSource::commit(const Commit &commit) {
    221   if (!commit.isCommitable())
    222     return false;
    223 
    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;
    227     switch (edit.Kind) {
    228     case edit::Commit::Act_Insert:
    229       commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
    230       break;
    231     case edit::Commit::Act_InsertFromRange:
    232       commitInsertFromRange(edit.OrigLoc, edit.Offset,
    233                             edit.InsertFromRangeOffs, edit.Length,
    234                             edit.BeforePrev);
    235       break;
    236     case edit::Commit::Act_Remove:
    237       commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
    238       break;
    239     }
    240   }
    241 
    242   return true;
    243 }
    244 
    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));
    251 }
    252 
    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))
    258     return false;
    259   if (isWhitespace(left) || isWhitespace(right))
    260     return true;
    261   if (canBeJoined(beforeWSpace, right, LangOpts))
    262     return false; // the whitespace was intentional, keep it.
    263   return true;
    264 }
    265 
    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.
    276 
    277   bool Invalid = false;
    278   StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
    279   if (Invalid)
    280     return;
    281 
    282   unsigned begin = offs.getOffset();
    283   unsigned end = begin + len;
    284 
    285   // FIXME: Remove newline.
    286 
    287   if (begin == 0) {
    288     if (buffer[end] == ' ')
    289       ++len;
    290     return;
    291   }
    292 
    293   if (buffer[end] == ' ') {
    294     if (canRemoveWhitespace(/*left=*/buffer[begin-1],
    295                             /*beforeWSpace=*/buffer[end-1],
    296                             /*right=*/buffer[end+1],
    297                             LangOpts))
    298       ++len;
    299     return;
    300   }
    301 
    302   if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
    303     text = " ";
    304 }
    305 
    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());
    313 
    314   if (text.empty())
    315     adjustRemoval(SM, LangOpts, Loc, offs, len, text);
    316 
    317   CharSourceRange range = CharSourceRange::getCharRange(Loc,
    318                                                      Loc.getLocWithOffset(len));
    319 
    320   if (text.empty()) {
    321     assert(len);
    322     receiver.remove(range);
    323     return;
    324   }
    325 
    326   if (len)
    327     receiver.replace(range, text);
    328   else
    329     receiver.insert(Loc, text);
    330 }
    331 
    332 void EditedSource::applyRewrites(EditsReceiver &receiver) {
    333   SmallString<128> StrVec;
    334   FileOffset CurOffs, CurEnd;
    335   unsigned CurLen;
    336 
    337   if (FileEdits.empty())
    338     return;
    339 
    340   FileEditsTy::iterator I = FileEdits.begin();
    341   CurOffs = I->first;
    342   StrVec = I->second.Text;
    343   CurLen = I->second.RemoveLen;
    344   CurEnd = CurOffs.getWithOffset(CurLen);
    345   ++I;
    346 
    347   for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
    348     FileOffset offs = I->first;
    349     FileEdit act = I->second;
    350     assert(offs >= CurEnd);
    351 
    352     if (offs == CurEnd) {
    353       StrVec += act.Text;
    354       CurLen += act.RemoveLen;
    355       CurEnd.getWithOffset(act.RemoveLen);
    356       continue;
    357     }
    358 
    359     applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
    360     CurOffs = offs;
    361     StrVec = act.Text;
    362     CurLen = act.RemoveLen;
    363     CurEnd = CurOffs.getWithOffset(CurLen);
    364   }
    365 
    366   applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
    367 }
    368 
    369 void EditedSource::clearRewrites() {
    370   FileEdits.clear();
    371   StrAlloc.Reset();
    372 }
    373 
    374 StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
    375                                       bool &Invalid) {
    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());
    381   SourceLocation
    382     ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
    383   return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
    384                               SourceMgr, LangOpts, &Invalid);
    385 }
    386 
    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();
    392   --I;
    393   FileEdit &FA = I->second;
    394   FileOffset B = I->first;
    395   FileOffset E = B.getWithOffset(FA.RemoveLen);
    396   if (Offs >= B && Offs < E)
    397     return I;
    398 
    399   return FileEdits.end();
    400 }
    401