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