Home | History | Annotate | Download | only in Edit
      1 //===----- Commit.cpp - A unit of 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/Commit.h"
     11 #include "clang/Basic/SourceManager.h"
     12 #include "clang/Edit/EditedSource.h"
     13 #include "clang/Lex/Lexer.h"
     14 #include "clang/Lex/PPConditionalDirectiveRecord.h"
     15 
     16 using namespace clang;
     17 using namespace edit;
     18 
     19 SourceLocation Commit::Edit::getFileLocation(SourceManager &SM) const {
     20   SourceLocation Loc = SM.getLocForStartOfFile(Offset.getFID());
     21   Loc = Loc.getLocWithOffset(Offset.getOffset());
     22   assert(Loc.isFileID());
     23   return Loc;
     24 }
     25 
     26 CharSourceRange Commit::Edit::getFileRange(SourceManager &SM) const {
     27   SourceLocation Loc = getFileLocation(SM);
     28   return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length));
     29 }
     30 
     31 CharSourceRange Commit::Edit::getInsertFromRange(SourceManager &SM) const {
     32   SourceLocation Loc = SM.getLocForStartOfFile(InsertFromRangeOffs.getFID());
     33   Loc = Loc.getLocWithOffset(InsertFromRangeOffs.getOffset());
     34   assert(Loc.isFileID());
     35   return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length));
     36 }
     37 
     38 Commit::Commit(EditedSource &Editor)
     39   : SourceMgr(Editor.getSourceManager()), LangOpts(Editor.getLangOpts()),
     40     PPRec(Editor.getPPCondDirectiveRecord()),
     41     Editor(&Editor), IsCommitable(true) { }
     42 
     43 bool Commit::insert(SourceLocation loc, StringRef text,
     44                     bool afterToken, bool beforePreviousInsertions) {
     45   if (text.empty())
     46     return true;
     47 
     48   FileOffset Offs;
     49   if ((!afterToken && !canInsert(loc, Offs)) ||
     50       ( afterToken && !canInsertAfterToken(loc, Offs, loc))) {
     51     IsCommitable = false;
     52     return false;
     53   }
     54 
     55   addInsert(loc, Offs, text, beforePreviousInsertions);
     56   return true;
     57 }
     58 
     59 bool Commit::insertFromRange(SourceLocation loc,
     60                              CharSourceRange range,
     61                              bool afterToken, bool beforePreviousInsertions) {
     62   FileOffset RangeOffs;
     63   unsigned RangeLen;
     64   if (!canRemoveRange(range, RangeOffs, RangeLen)) {
     65     IsCommitable = false;
     66     return false;
     67   }
     68 
     69   FileOffset Offs;
     70   if ((!afterToken && !canInsert(loc, Offs)) ||
     71       ( afterToken && !canInsertAfterToken(loc, Offs, loc))) {
     72     IsCommitable = false;
     73     return false;
     74   }
     75 
     76   if (PPRec &&
     77       PPRec->areInDifferentConditionalDirectiveRegion(loc, range.getBegin())) {
     78     IsCommitable = false;
     79     return false;
     80   }
     81 
     82   addInsertFromRange(loc, Offs, RangeOffs, RangeLen, beforePreviousInsertions);
     83   return true;
     84 }
     85 
     86 bool Commit::remove(CharSourceRange range) {
     87   FileOffset Offs;
     88   unsigned Len;
     89   if (!canRemoveRange(range, Offs, Len)) {
     90     IsCommitable = false;
     91     return false;
     92   }
     93 
     94   addRemove(range.getBegin(), Offs, Len);
     95   return true;
     96 }
     97 
     98 bool Commit::insertWrap(StringRef before, CharSourceRange range,
     99                         StringRef after) {
    100   bool commitableBefore = insert(range.getBegin(), before, /*afterToken=*/false,
    101                                  /*beforePreviousInsertions=*/true);
    102   bool commitableAfter;
    103   if (range.isTokenRange())
    104     commitableAfter = insertAfterToken(range.getEnd(), after);
    105   else
    106     commitableAfter = insert(range.getEnd(), after);
    107 
    108   return commitableBefore && commitableAfter;
    109 }
    110 
    111 bool Commit::replace(CharSourceRange range, StringRef text) {
    112   if (text.empty())
    113     return remove(range);
    114 
    115   FileOffset Offs;
    116   unsigned Len;
    117   if (!canInsert(range.getBegin(), Offs) || !canRemoveRange(range, Offs, Len)) {
    118     IsCommitable = false;
    119     return false;
    120   }
    121 
    122   addRemove(range.getBegin(), Offs, Len);
    123   addInsert(range.getBegin(), Offs, text, false);
    124   return true;
    125 }
    126 
    127 bool Commit::replaceWithInner(CharSourceRange range,
    128                               CharSourceRange replacementRange) {
    129   FileOffset OuterBegin;
    130   unsigned OuterLen;
    131   if (!canRemoveRange(range, OuterBegin, OuterLen)) {
    132     IsCommitable = false;
    133     return false;
    134   }
    135 
    136   FileOffset InnerBegin;
    137   unsigned InnerLen;
    138   if (!canRemoveRange(replacementRange, InnerBegin, InnerLen)) {
    139     IsCommitable = false;
    140     return false;
    141   }
    142 
    143   FileOffset OuterEnd = OuterBegin.getWithOffset(OuterLen);
    144   FileOffset InnerEnd = InnerBegin.getWithOffset(InnerLen);
    145   if (OuterBegin.getFID() != InnerBegin.getFID() ||
    146       InnerBegin < OuterBegin ||
    147       InnerBegin > OuterEnd ||
    148       InnerEnd > OuterEnd) {
    149     IsCommitable = false;
    150     return false;
    151   }
    152 
    153   addRemove(range.getBegin(),
    154             OuterBegin, InnerBegin.getOffset() - OuterBegin.getOffset());
    155   addRemove(replacementRange.getEnd(),
    156             InnerEnd, OuterEnd.getOffset() - InnerEnd.getOffset());
    157   return true;
    158 }
    159 
    160 bool Commit::replaceText(SourceLocation loc, StringRef text,
    161                          StringRef replacementText) {
    162   if (text.empty() || replacementText.empty())
    163     return true;
    164 
    165   FileOffset Offs;
    166   unsigned Len;
    167   if (!canReplaceText(loc, replacementText, Offs, Len)) {
    168     IsCommitable = false;
    169     return false;
    170   }
    171 
    172   addRemove(loc, Offs, Len);
    173   addInsert(loc, Offs, text, false);
    174   return true;
    175 }
    176 
    177 void Commit::addInsert(SourceLocation OrigLoc, FileOffset Offs, StringRef text,
    178                        bool beforePreviousInsertions) {
    179   if (text.empty())
    180     return;
    181 
    182   Edit data;
    183   data.Kind = Act_Insert;
    184   data.OrigLoc = OrigLoc;
    185   data.Offset = Offs;
    186   data.Text = text;
    187   data.BeforePrev = beforePreviousInsertions;
    188   CachedEdits.push_back(data);
    189 }
    190 
    191 void Commit::addInsertFromRange(SourceLocation OrigLoc, FileOffset Offs,
    192                                 FileOffset RangeOffs, unsigned RangeLen,
    193                                 bool beforePreviousInsertions) {
    194   if (RangeLen == 0)
    195     return;
    196 
    197   Edit data;
    198   data.Kind = Act_InsertFromRange;
    199   data.OrigLoc = OrigLoc;
    200   data.Offset = Offs;
    201   data.InsertFromRangeOffs = RangeOffs;
    202   data.Length = RangeLen;
    203   data.BeforePrev = beforePreviousInsertions;
    204   CachedEdits.push_back(data);
    205 }
    206 
    207 void Commit::addRemove(SourceLocation OrigLoc,
    208                        FileOffset Offs, unsigned Len) {
    209   if (Len == 0)
    210     return;
    211 
    212   Edit data;
    213   data.Kind = Act_Remove;
    214   data.OrigLoc = OrigLoc;
    215   data.Offset = Offs;
    216   data.Length = Len;
    217   CachedEdits.push_back(data);
    218 }
    219 
    220 bool Commit::canInsert(SourceLocation loc, FileOffset &offs) {
    221   if (loc.isInvalid())
    222     return false;
    223 
    224   if (loc.isMacroID())
    225     isAtStartOfMacroExpansion(loc, &loc);
    226 
    227   const SourceManager &SM = SourceMgr;
    228   while (SM.isMacroArgExpansion(loc))
    229     loc = SM.getImmediateSpellingLoc(loc);
    230 
    231   if (loc.isMacroID())
    232     if (!isAtStartOfMacroExpansion(loc, &loc))
    233       return false;
    234 
    235   if (SM.isInSystemHeader(loc))
    236     return false;
    237 
    238   std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc);
    239   if (locInfo.first.isInvalid())
    240     return false;
    241   offs = FileOffset(locInfo.first, locInfo.second);
    242   return canInsertInOffset(loc, offs);
    243 }
    244 
    245 bool Commit::canInsertAfterToken(SourceLocation loc, FileOffset &offs,
    246                                  SourceLocation &AfterLoc) {
    247   if (loc.isInvalid())
    248 
    249     return false;
    250 
    251   SourceLocation spellLoc = SourceMgr.getSpellingLoc(loc);
    252   unsigned tokLen = Lexer::MeasureTokenLength(spellLoc, SourceMgr, LangOpts);
    253   AfterLoc = loc.getLocWithOffset(tokLen);
    254 
    255   if (loc.isMacroID())
    256     isAtEndOfMacroExpansion(loc, &loc);
    257 
    258   const SourceManager &SM = SourceMgr;
    259   while (SM.isMacroArgExpansion(loc))
    260     loc = SM.getImmediateSpellingLoc(loc);
    261 
    262   if (loc.isMacroID())
    263     if (!isAtEndOfMacroExpansion(loc, &loc))
    264       return false;
    265 
    266   if (SM.isInSystemHeader(loc))
    267     return false;
    268 
    269   loc = Lexer::getLocForEndOfToken(loc, 0, SourceMgr, LangOpts);
    270   if (loc.isInvalid())
    271     return false;
    272 
    273   std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc);
    274   if (locInfo.first.isInvalid())
    275     return false;
    276   offs = FileOffset(locInfo.first, locInfo.second);
    277   return canInsertInOffset(loc, offs);
    278 }
    279 
    280 bool Commit::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
    281   for (unsigned i = 0, e = CachedEdits.size(); i != e; ++i) {
    282     Edit &act = CachedEdits[i];
    283     if (act.Kind == Act_Remove) {
    284       if (act.Offset.getFID() == Offs.getFID() &&
    285           Offs > act.Offset && Offs < act.Offset.getWithOffset(act.Length))
    286         return false; // position has been removed.
    287     }
    288   }
    289 
    290   if (!Editor)
    291     return true;
    292   return Editor->canInsertInOffset(OrigLoc, Offs);
    293 }
    294 
    295 bool Commit::canRemoveRange(CharSourceRange range,
    296                             FileOffset &Offs, unsigned &Len) {
    297   const SourceManager &SM = SourceMgr;
    298   range = Lexer::makeFileCharRange(range, SM, LangOpts);
    299   if (range.isInvalid())
    300     return false;
    301 
    302   if (range.getBegin().isMacroID() || range.getEnd().isMacroID())
    303     return false;
    304   if (SM.isInSystemHeader(range.getBegin()) ||
    305       SM.isInSystemHeader(range.getEnd()))
    306     return false;
    307 
    308   if (PPRec && PPRec->rangeIntersectsConditionalDirective(range.getAsRange()))
    309     return false;
    310 
    311   std::pair<FileID, unsigned> beginInfo = SM.getDecomposedLoc(range.getBegin());
    312   std::pair<FileID, unsigned> endInfo = SM.getDecomposedLoc(range.getEnd());
    313   if (beginInfo.first != endInfo.first ||
    314       beginInfo.second > endInfo.second)
    315     return false;
    316 
    317   Offs = FileOffset(beginInfo.first, beginInfo.second);
    318   Len = endInfo.second - beginInfo.second;
    319   return true;
    320 }
    321 
    322 bool Commit::canReplaceText(SourceLocation loc, StringRef text,
    323                             FileOffset &Offs, unsigned &Len) {
    324   assert(!text.empty());
    325 
    326   if (!canInsert(loc, Offs))
    327     return false;
    328 
    329   // Try to load the file buffer.
    330   bool invalidTemp = false;
    331   StringRef file = SourceMgr.getBufferData(Offs.getFID(), &invalidTemp);
    332   if (invalidTemp)
    333     return false;
    334 
    335   Len = text.size();
    336   return file.substr(Offs.getOffset()).startswith(text);
    337 }
    338 
    339 bool Commit::isAtStartOfMacroExpansion(SourceLocation loc,
    340                                        SourceLocation *MacroBegin) const {
    341   return Lexer::isAtStartOfMacroExpansion(loc, SourceMgr, LangOpts, MacroBegin);
    342 }
    343 bool Commit::isAtEndOfMacroExpansion(SourceLocation loc,
    344                                      SourceLocation *MacroEnd) const {
    345   return Lexer::isAtEndOfMacroExpansion(loc, SourceMgr, LangOpts, MacroEnd);
    346 }
    347