Home | History | Annotate | Download | only in Rewrite
      1 //===--- Rewriter.cpp - Code rewriting interface --------------------------===//
      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 //  This file defines the Rewriter class, which is used for code
     11 //  transformations.
     12 //
     13 //===----------------------------------------------------------------------===//
     14 
     15 #include "clang/Rewrite/Core/Rewriter.h"
     16 #include "clang/Basic/Diagnostic.h"
     17 #include "clang/Basic/DiagnosticIDs.h"
     18 #include "clang/Basic/SourceManager.h"
     19 #include "clang/Lex/Lexer.h"
     20 #include "llvm/ADT/SmallString.h"
     21 #include "llvm/Support/FileSystem.h"
     22 #include "llvm/Support/raw_ostream.h"
     23 using namespace clang;
     24 
     25 raw_ostream &RewriteBuffer::write(raw_ostream &os) const {
     26   // Walk RewriteRope chunks efficiently using MoveToNextPiece() instead of the
     27   // character iterator.
     28   for (RopePieceBTreeIterator I = begin(), E = end(); I != E;
     29        I.MoveToNextPiece())
     30     os << I.piece();
     31   return os;
     32 }
     33 
     34 /// \brief Return true if this character is non-new-line whitespace:
     35 /// ' ', '\\t', '\\f', '\\v', '\\r'.
     36 static inline bool isWhitespaceExceptNL(unsigned char c) {
     37   switch (c) {
     38   case ' ':
     39   case '\t':
     40   case '\f':
     41   case '\v':
     42   case '\r':
     43     return true;
     44   default:
     45     return false;
     46   }
     47 }
     48 
     49 void RewriteBuffer::RemoveText(unsigned OrigOffset, unsigned Size,
     50                                bool removeLineIfEmpty) {
     51   // Nothing to remove, exit early.
     52   if (Size == 0) return;
     53 
     54   unsigned RealOffset = getMappedOffset(OrigOffset, true);
     55   assert(RealOffset+Size <= Buffer.size() && "Invalid location");
     56 
     57   // Remove the dead characters.
     58   Buffer.erase(RealOffset, Size);
     59 
     60   // Add a delta so that future changes are offset correctly.
     61   AddReplaceDelta(OrigOffset, -Size);
     62 
     63   if (removeLineIfEmpty) {
     64     // Find the line that the remove occurred and if it is completely empty
     65     // remove the line as well.
     66 
     67     iterator curLineStart = begin();
     68     unsigned curLineStartOffs = 0;
     69     iterator posI = begin();
     70     for (unsigned i = 0; i != RealOffset; ++i) {
     71       if (*posI == '\n') {
     72         curLineStart = posI;
     73         ++curLineStart;
     74         curLineStartOffs = i + 1;
     75       }
     76       ++posI;
     77     }
     78 
     79     unsigned lineSize = 0;
     80     posI = curLineStart;
     81     while (posI != end() && isWhitespaceExceptNL(*posI)) {
     82       ++posI;
     83       ++lineSize;
     84     }
     85     if (posI != end() && *posI == '\n') {
     86       Buffer.erase(curLineStartOffs, lineSize + 1/* + '\n'*/);
     87       AddReplaceDelta(curLineStartOffs, -(lineSize + 1/* + '\n'*/));
     88     }
     89   }
     90 }
     91 
     92 void RewriteBuffer::InsertText(unsigned OrigOffset, StringRef Str,
     93                                bool InsertAfter) {
     94 
     95   // Nothing to insert, exit early.
     96   if (Str.empty()) return;
     97 
     98   unsigned RealOffset = getMappedOffset(OrigOffset, InsertAfter);
     99   Buffer.insert(RealOffset, Str.begin(), Str.end());
    100 
    101   // Add a delta so that future changes are offset correctly.
    102   AddInsertDelta(OrigOffset, Str.size());
    103 }
    104 
    105 /// ReplaceText - This method replaces a range of characters in the input
    106 /// buffer with a new string.  This is effectively a combined "remove+insert"
    107 /// operation.
    108 void RewriteBuffer::ReplaceText(unsigned OrigOffset, unsigned OrigLength,
    109                                 StringRef NewStr) {
    110   unsigned RealOffset = getMappedOffset(OrigOffset, true);
    111   Buffer.erase(RealOffset, OrigLength);
    112   Buffer.insert(RealOffset, NewStr.begin(), NewStr.end());
    113   if (OrigLength != NewStr.size())
    114     AddReplaceDelta(OrigOffset, NewStr.size() - OrigLength);
    115 }
    116 
    117 
    118 //===----------------------------------------------------------------------===//
    119 // Rewriter class
    120 //===----------------------------------------------------------------------===//
    121 
    122 /// getRangeSize - Return the size in bytes of the specified range if they
    123 /// are in the same file.  If not, this returns -1.
    124 int Rewriter::getRangeSize(const CharSourceRange &Range,
    125                            RewriteOptions opts) const {
    126   if (!isRewritable(Range.getBegin()) ||
    127       !isRewritable(Range.getEnd())) return -1;
    128 
    129   FileID StartFileID, EndFileID;
    130   unsigned StartOff, EndOff;
    131 
    132   StartOff = getLocationOffsetAndFileID(Range.getBegin(), StartFileID);
    133   EndOff   = getLocationOffsetAndFileID(Range.getEnd(), EndFileID);
    134 
    135   if (StartFileID != EndFileID)
    136     return -1;
    137 
    138   // If edits have been made to this buffer, the delta between the range may
    139   // have changed.
    140   std::map<FileID, RewriteBuffer>::const_iterator I =
    141     RewriteBuffers.find(StartFileID);
    142   if (I != RewriteBuffers.end()) {
    143     const RewriteBuffer &RB = I->second;
    144     EndOff = RB.getMappedOffset(EndOff, opts.IncludeInsertsAtEndOfRange);
    145     StartOff = RB.getMappedOffset(StartOff, !opts.IncludeInsertsAtBeginOfRange);
    146   }
    147 
    148 
    149   // Adjust the end offset to the end of the last token, instead of being the
    150   // start of the last token if this is a token range.
    151   if (Range.isTokenRange())
    152     EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts);
    153 
    154   return EndOff-StartOff;
    155 }
    156 
    157 int Rewriter::getRangeSize(SourceRange Range, RewriteOptions opts) const {
    158   return getRangeSize(CharSourceRange::getTokenRange(Range), opts);
    159 }
    160 
    161 
    162 /// getRewrittenText - Return the rewritten form of the text in the specified
    163 /// range.  If the start or end of the range was unrewritable or if they are
    164 /// in different buffers, this returns an empty string.
    165 ///
    166 /// Note that this method is not particularly efficient.
    167 ///
    168 std::string Rewriter::getRewrittenText(SourceRange Range) const {
    169   if (!isRewritable(Range.getBegin()) ||
    170       !isRewritable(Range.getEnd()))
    171     return "";
    172 
    173   FileID StartFileID, EndFileID;
    174   unsigned StartOff, EndOff;
    175   StartOff = getLocationOffsetAndFileID(Range.getBegin(), StartFileID);
    176   EndOff   = getLocationOffsetAndFileID(Range.getEnd(), EndFileID);
    177 
    178   if (StartFileID != EndFileID)
    179     return ""; // Start and end in different buffers.
    180 
    181   // If edits have been made to this buffer, the delta between the range may
    182   // have changed.
    183   std::map<FileID, RewriteBuffer>::const_iterator I =
    184     RewriteBuffers.find(StartFileID);
    185   if (I == RewriteBuffers.end()) {
    186     // If the buffer hasn't been rewritten, just return the text from the input.
    187     const char *Ptr = SourceMgr->getCharacterData(Range.getBegin());
    188 
    189     // Adjust the end offset to the end of the last token, instead of being the
    190     // start of the last token.
    191     EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts);
    192     return std::string(Ptr, Ptr+EndOff-StartOff);
    193   }
    194 
    195   const RewriteBuffer &RB = I->second;
    196   EndOff = RB.getMappedOffset(EndOff, true);
    197   StartOff = RB.getMappedOffset(StartOff);
    198 
    199   // Adjust the end offset to the end of the last token, instead of being the
    200   // start of the last token.
    201   EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts);
    202 
    203   // Advance the iterators to the right spot, yay for linear time algorithms.
    204   RewriteBuffer::iterator Start = RB.begin();
    205   std::advance(Start, StartOff);
    206   RewriteBuffer::iterator End = Start;
    207   std::advance(End, EndOff-StartOff);
    208 
    209   return std::string(Start, End);
    210 }
    211 
    212 unsigned Rewriter::getLocationOffsetAndFileID(SourceLocation Loc,
    213                                               FileID &FID) const {
    214   assert(Loc.isValid() && "Invalid location");
    215   std::pair<FileID,unsigned> V = SourceMgr->getDecomposedLoc(Loc);
    216   FID = V.first;
    217   return V.second;
    218 }
    219 
    220 
    221 /// getEditBuffer - Get or create a RewriteBuffer for the specified FileID.
    222 ///
    223 RewriteBuffer &Rewriter::getEditBuffer(FileID FID) {
    224   std::map<FileID, RewriteBuffer>::iterator I =
    225     RewriteBuffers.lower_bound(FID);
    226   if (I != RewriteBuffers.end() && I->first == FID)
    227     return I->second;
    228   I = RewriteBuffers.insert(I, std::make_pair(FID, RewriteBuffer()));
    229 
    230   StringRef MB = SourceMgr->getBufferData(FID);
    231   I->second.Initialize(MB.begin(), MB.end());
    232 
    233   return I->second;
    234 }
    235 
    236 /// InsertText - Insert the specified string at the specified location in the
    237 /// original buffer.
    238 bool Rewriter::InsertText(SourceLocation Loc, StringRef Str,
    239                           bool InsertAfter, bool indentNewLines) {
    240   if (!isRewritable(Loc)) return true;
    241   FileID FID;
    242   unsigned StartOffs = getLocationOffsetAndFileID(Loc, FID);
    243 
    244   SmallString<128> indentedStr;
    245   if (indentNewLines && Str.find('\n') != StringRef::npos) {
    246     StringRef MB = SourceMgr->getBufferData(FID);
    247 
    248     unsigned lineNo = SourceMgr->getLineNumber(FID, StartOffs) - 1;
    249     const SrcMgr::ContentCache *
    250         Content = SourceMgr->getSLocEntry(FID).getFile().getContentCache();
    251     unsigned lineOffs = Content->SourceLineCache[lineNo];
    252 
    253     // Find the whitespace at the start of the line.
    254     StringRef indentSpace;
    255     {
    256       unsigned i = lineOffs;
    257       while (isWhitespaceExceptNL(MB[i]))
    258         ++i;
    259       indentSpace = MB.substr(lineOffs, i-lineOffs);
    260     }
    261 
    262     SmallVector<StringRef, 4> lines;
    263     Str.split(lines, "\n");
    264 
    265     for (unsigned i = 0, e = lines.size(); i != e; ++i) {
    266       indentedStr += lines[i];
    267       if (i < e-1) {
    268         indentedStr += '\n';
    269         indentedStr += indentSpace;
    270       }
    271     }
    272     Str = indentedStr.str();
    273   }
    274 
    275   getEditBuffer(FID).InsertText(StartOffs, Str, InsertAfter);
    276   return false;
    277 }
    278 
    279 bool Rewriter::InsertTextAfterToken(SourceLocation Loc, StringRef Str) {
    280   if (!isRewritable(Loc)) return true;
    281   FileID FID;
    282   unsigned StartOffs = getLocationOffsetAndFileID(Loc, FID);
    283   RewriteOptions rangeOpts;
    284   rangeOpts.IncludeInsertsAtBeginOfRange = false;
    285   StartOffs += getRangeSize(SourceRange(Loc, Loc), rangeOpts);
    286   getEditBuffer(FID).InsertText(StartOffs, Str, /*InsertAfter*/true);
    287   return false;
    288 }
    289 
    290 /// RemoveText - Remove the specified text region.
    291 bool Rewriter::RemoveText(SourceLocation Start, unsigned Length,
    292                           RewriteOptions opts) {
    293   if (!isRewritable(Start)) return true;
    294   FileID FID;
    295   unsigned StartOffs = getLocationOffsetAndFileID(Start, FID);
    296   getEditBuffer(FID).RemoveText(StartOffs, Length, opts.RemoveLineIfEmpty);
    297   return false;
    298 }
    299 
    300 /// ReplaceText - This method replaces a range of characters in the input
    301 /// buffer with a new string.  This is effectively a combined "remove/insert"
    302 /// operation.
    303 bool Rewriter::ReplaceText(SourceLocation Start, unsigned OrigLength,
    304                            StringRef NewStr) {
    305   if (!isRewritable(Start)) return true;
    306   FileID StartFileID;
    307   unsigned StartOffs = getLocationOffsetAndFileID(Start, StartFileID);
    308 
    309   getEditBuffer(StartFileID).ReplaceText(StartOffs, OrigLength, NewStr);
    310   return false;
    311 }
    312 
    313 bool Rewriter::ReplaceText(SourceRange range, SourceRange replacementRange) {
    314   if (!isRewritable(range.getBegin())) return true;
    315   if (!isRewritable(range.getEnd())) return true;
    316   if (replacementRange.isInvalid()) return true;
    317   SourceLocation start = range.getBegin();
    318   unsigned origLength = getRangeSize(range);
    319   unsigned newLength = getRangeSize(replacementRange);
    320   FileID FID;
    321   unsigned newOffs = getLocationOffsetAndFileID(replacementRange.getBegin(),
    322                                                 FID);
    323   StringRef MB = SourceMgr->getBufferData(FID);
    324   return ReplaceText(start, origLength, MB.substr(newOffs, newLength));
    325 }
    326 
    327 bool Rewriter::IncreaseIndentation(CharSourceRange range,
    328                                    SourceLocation parentIndent) {
    329   if (range.isInvalid()) return true;
    330   if (!isRewritable(range.getBegin())) return true;
    331   if (!isRewritable(range.getEnd())) return true;
    332   if (!isRewritable(parentIndent)) return true;
    333 
    334   FileID StartFileID, EndFileID, parentFileID;
    335   unsigned StartOff, EndOff, parentOff;
    336 
    337   StartOff = getLocationOffsetAndFileID(range.getBegin(), StartFileID);
    338   EndOff   = getLocationOffsetAndFileID(range.getEnd(), EndFileID);
    339   parentOff = getLocationOffsetAndFileID(parentIndent, parentFileID);
    340 
    341   if (StartFileID != EndFileID || StartFileID != parentFileID)
    342     return true;
    343   if (StartOff > EndOff)
    344     return true;
    345 
    346   FileID FID = StartFileID;
    347   StringRef MB = SourceMgr->getBufferData(FID);
    348 
    349   unsigned parentLineNo = SourceMgr->getLineNumber(FID, parentOff) - 1;
    350   unsigned startLineNo = SourceMgr->getLineNumber(FID, StartOff) - 1;
    351   unsigned endLineNo = SourceMgr->getLineNumber(FID, EndOff) - 1;
    352 
    353   const SrcMgr::ContentCache *
    354       Content = SourceMgr->getSLocEntry(FID).getFile().getContentCache();
    355 
    356   // Find where the lines start.
    357   unsigned parentLineOffs = Content->SourceLineCache[parentLineNo];
    358   unsigned startLineOffs = Content->SourceLineCache[startLineNo];
    359 
    360   // Find the whitespace at the start of each line.
    361   StringRef parentSpace, startSpace;
    362   {
    363     unsigned i = parentLineOffs;
    364     while (isWhitespaceExceptNL(MB[i]))
    365       ++i;
    366     parentSpace = MB.substr(parentLineOffs, i-parentLineOffs);
    367 
    368     i = startLineOffs;
    369     while (isWhitespaceExceptNL(MB[i]))
    370       ++i;
    371     startSpace = MB.substr(startLineOffs, i-startLineOffs);
    372   }
    373   if (parentSpace.size() >= startSpace.size())
    374     return true;
    375   if (!startSpace.startswith(parentSpace))
    376     return true;
    377 
    378   StringRef indent = startSpace.substr(parentSpace.size());
    379 
    380   // Indent the lines between start/end offsets.
    381   RewriteBuffer &RB = getEditBuffer(FID);
    382   for (unsigned lineNo = startLineNo; lineNo <= endLineNo; ++lineNo) {
    383     unsigned offs = Content->SourceLineCache[lineNo];
    384     unsigned i = offs;
    385     while (isWhitespaceExceptNL(MB[i]))
    386       ++i;
    387     StringRef origIndent = MB.substr(offs, i-offs);
    388     if (origIndent.startswith(startSpace))
    389       RB.InsertText(offs, indent, /*InsertAfter=*/false);
    390   }
    391 
    392   return false;
    393 }
    394 
    395 namespace {
    396 // A wrapper for a file stream that atomically overwrites the target.
    397 //
    398 // Creates a file output stream for a temporary file in the constructor,
    399 // which is later accessible via getStream() if ok() return true.
    400 // Flushes the stream and moves the temporary file to the target location
    401 // in the destructor.
    402 class AtomicallyMovedFile {
    403 public:
    404   AtomicallyMovedFile(DiagnosticsEngine &Diagnostics, StringRef Filename,
    405                       bool &AllWritten)
    406     : Diagnostics(Diagnostics), Filename(Filename), AllWritten(AllWritten) {
    407     TempFilename = Filename;
    408     TempFilename += "-%%%%%%%%";
    409     int FD;
    410     if (llvm::sys::fs::createUniqueFile(TempFilename, FD, TempFilename)) {
    411       AllWritten = false;
    412       Diagnostics.Report(clang::diag::err_unable_to_make_temp)
    413         << TempFilename;
    414     } else {
    415       FileStream.reset(new llvm::raw_fd_ostream(FD, /*shouldClose=*/true));
    416     }
    417   }
    418 
    419   ~AtomicallyMovedFile() {
    420     if (!ok()) return;
    421 
    422     // Close (will also flush) theFileStream.
    423     FileStream->close();
    424     if (std::error_code ec = llvm::sys::fs::rename(TempFilename, Filename)) {
    425       AllWritten = false;
    426       Diagnostics.Report(clang::diag::err_unable_to_rename_temp)
    427         << TempFilename << Filename << ec.message();
    428       // If the remove fails, there's not a lot we can do - this is already an
    429       // error.
    430       llvm::sys::fs::remove(TempFilename);
    431     }
    432   }
    433 
    434   bool ok() { return (bool)FileStream; }
    435   raw_ostream &getStream() { return *FileStream; }
    436 
    437 private:
    438   DiagnosticsEngine &Diagnostics;
    439   StringRef Filename;
    440   SmallString<128> TempFilename;
    441   std::unique_ptr<llvm::raw_fd_ostream> FileStream;
    442   bool &AllWritten;
    443 };
    444 } // end anonymous namespace
    445 
    446 bool Rewriter::overwriteChangedFiles() {
    447   bool AllWritten = true;
    448   for (buffer_iterator I = buffer_begin(), E = buffer_end(); I != E; ++I) {
    449     const FileEntry *Entry =
    450         getSourceMgr().getFileEntryForID(I->first);
    451     AtomicallyMovedFile File(getSourceMgr().getDiagnostics(), Entry->getName(),
    452                              AllWritten);
    453     if (File.ok()) {
    454       I->second.write(File.getStream());
    455     }
    456   }
    457   return !AllWritten;
    458 }
    459