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