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