1 //===--- Rewriter.cpp - Code rewriting interface --------------------------===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
10 // This file defines the Rewriter class, which is used for code
13 //===----------------------------------------------------------------------===//
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
;
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
;
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
) {
51 void RewriteBuffer::RemoveText(unsigned OrigOffset
, unsigned Size
,
52 bool removeLineIfEmpty
) {
53 // Nothing to remove, exit early.
54 if (Size
== 0) return;
56 unsigned RealOffset
= getMappedOffset(OrigOffset
, true);
57 assert(RealOffset
+Size
< Buffer
.size() && "Invalid location");
59 // Remove the dead characters.
60 Buffer
.erase(RealOffset
, Size
);
62 // Add a delta so that future changes are offset correctly.
63 AddReplaceDelta(OrigOffset
, -Size
);
65 if (removeLineIfEmpty
) {
66 // Find the line that the remove occurred and if it is completely empty
67 // remove the line as well.
69 iterator curLineStart
= begin();
70 unsigned curLineStartOffs
= 0;
71 iterator posI
= begin();
72 for (unsigned i
= 0; i
!= RealOffset
; ++i
) {
76 curLineStartOffs
= i
+ 1;
81 unsigned lineSize
= 0;
83 while (posI
!= end() && isWhitespace(*posI
)) {
87 if (posI
!= end() && *posI
== '\n') {
88 Buffer
.erase(curLineStartOffs
, lineSize
+ 1/* + '\n'*/);
89 AddReplaceDelta(curLineStartOffs
, -(lineSize
+ 1/* + '\n'*/));
94 void RewriteBuffer::InsertText(unsigned OrigOffset
, StringRef Str
,
97 // Nothing to insert, exit early.
98 if (Str
.empty()) return;
100 unsigned RealOffset
= getMappedOffset(OrigOffset
, InsertAfter
);
101 Buffer
.insert(RealOffset
, Str
.begin(), Str
.end());
103 // Add a delta so that future changes are offset correctly.
104 AddInsertDelta(OrigOffset
, Str
.size());
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"
110 void RewriteBuffer::ReplaceText(unsigned OrigOffset
, unsigned OrigLength
,
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
);
120 //===----------------------------------------------------------------------===//
122 //===----------------------------------------------------------------------===//
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;
131 FileID StartFileID
, EndFileID
;
132 unsigned StartOff
, EndOff
;
134 StartOff
= getLocationOffsetAndFileID(Range
.getBegin(), StartFileID
);
135 EndOff
= getLocationOffsetAndFileID(Range
.getEnd(), EndFileID
);
137 if (StartFileID
!= EndFileID
)
140 // If edits have been made to this buffer, the delta between the range may
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
);
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
);
156 return EndOff
-StartOff
;
159 int Rewriter::getRangeSize(SourceRange Range
, RewriteOptions opts
) const {
160 return getRangeSize(CharSourceRange::getTokenRange(Range
), opts
);
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.
168 /// Note that this method is not particularly efficient.
170 std::string
Rewriter::getRewrittenText(SourceRange Range
) const {
171 if (!isRewritable(Range
.getBegin()) ||
172 !isRewritable(Range
.getEnd()))
175 FileID StartFileID
, EndFileID
;
176 unsigned StartOff
, EndOff
;
177 StartOff
= getLocationOffsetAndFileID(Range
.getBegin(), StartFileID
);
178 EndOff
= getLocationOffsetAndFileID(Range
.getEnd(), EndFileID
);
180 if (StartFileID
!= EndFileID
)
181 return ""; // Start and end in different buffers.
183 // If edits have been made to this buffer, the delta between the range may
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());
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
);
197 const RewriteBuffer
&RB
= I
->second
;
198 EndOff
= RB
.getMappedOffset(EndOff
, true);
199 StartOff
= RB
.getMappedOffset(StartOff
);
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
);
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
);
211 return std::string(Start
, End
);
214 unsigned Rewriter::getLocationOffsetAndFileID(SourceLocation Loc
,
216 assert(Loc
.isValid() && "Invalid location");
217 std::pair
<FileID
,unsigned> V
= SourceMgr
->getDecomposedLoc(Loc
);
223 /// getEditBuffer - Get or create a RewriteBuffer for the specified FileID.
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
)
230 I
= RewriteBuffers
.insert(I
, std::make_pair(FID
, RewriteBuffer()));
232 StringRef MB
= SourceMgr
->getBufferData(FID
);
233 I
->second
.Initialize(MB
.begin(), MB
.end());
238 /// InsertText - Insert the specified string at the specified location in the
240 bool Rewriter::InsertText(SourceLocation Loc
, StringRef Str
,
241 bool InsertAfter
, bool indentNewLines
) {
242 if (!isRewritable(Loc
)) return true;
244 unsigned StartOffs
= getLocationOffsetAndFileID(Loc
, FID
);
246 SmallString
<128> indentedStr
;
247 if (indentNewLines
&& Str
.find('\n') != StringRef::npos
) {
248 StringRef MB
= SourceMgr
->getBufferData(FID
);
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
];
255 // Find the whitespace at the start of the line.
256 StringRef indentSpace
;
258 unsigned i
= lineOffs
;
259 while (isWhitespace(MB
[i
]))
261 indentSpace
= MB
.substr(lineOffs
, i
-lineOffs
);
264 SmallVector
<StringRef
, 4> lines
;
265 Str
.split(lines
, "\n");
267 for (unsigned i
= 0, e
= lines
.size(); i
!= e
; ++i
) {
268 indentedStr
+= lines
[i
];
271 indentedStr
+= indentSpace
;
274 Str
= indentedStr
.str();
277 getEditBuffer(FID
).InsertText(StartOffs
, Str
, InsertAfter
);
281 bool Rewriter::InsertTextAfterToken(SourceLocation Loc
, StringRef Str
) {
282 if (!isRewritable(Loc
)) return true;
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);
292 /// RemoveText - Remove the specified text region.
293 bool Rewriter::RemoveText(SourceLocation Start
, unsigned Length
,
294 RewriteOptions opts
) {
295 if (!isRewritable(Start
)) return true;
297 unsigned StartOffs
= getLocationOffsetAndFileID(Start
, FID
);
298 getEditBuffer(FID
).RemoveText(StartOffs
, Length
, opts
.RemoveLineIfEmpty
);
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"
305 bool Rewriter::ReplaceText(SourceLocation Start
, unsigned OrigLength
,
307 if (!isRewritable(Start
)) return true;
309 unsigned StartOffs
= getLocationOffsetAndFileID(Start
, StartFileID
);
311 getEditBuffer(StartFileID
).ReplaceText(StartOffs
, OrigLength
, NewStr
);
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
);
323 unsigned newOffs
= getLocationOffsetAndFileID(replacementRange
.getBegin(),
325 StringRef MB
= SourceMgr
->getBufferData(FID
);
326 return ReplaceText(start
, origLength
, MB
.substr(newOffs
, newLength
));
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;
336 FileID StartFileID
, EndFileID
, parentFileID
;
337 unsigned StartOff
, EndOff
, parentOff
;
339 StartOff
= getLocationOffsetAndFileID(range
.getBegin(), StartFileID
);
340 EndOff
= getLocationOffsetAndFileID(range
.getEnd(), EndFileID
);
341 parentOff
= getLocationOffsetAndFileID(parentIndent
, parentFileID
);
343 if (StartFileID
!= EndFileID
|| StartFileID
!= parentFileID
)
345 if (StartOff
> EndOff
)
348 FileID FID
= StartFileID
;
349 StringRef MB
= SourceMgr
->getBufferData(FID
);
351 unsigned parentLineNo
= SourceMgr
->getLineNumber(FID
, parentOff
) - 1;
352 unsigned startLineNo
= SourceMgr
->getLineNumber(FID
, StartOff
) - 1;
353 unsigned endLineNo
= SourceMgr
->getLineNumber(FID
, EndOff
) - 1;
355 const SrcMgr::ContentCache
*
356 Content
= SourceMgr
->getSLocEntry(FID
).getFile().getContentCache();
358 // Find where the lines start.
359 unsigned parentLineOffs
= Content
->SourceLineCache
[parentLineNo
];
360 unsigned startLineOffs
= Content
->SourceLineCache
[startLineNo
];
362 // Find the whitespace at the start of each line.
363 StringRef parentSpace
, startSpace
;
365 unsigned i
= parentLineOffs
;
366 while (isWhitespace(MB
[i
]))
368 parentSpace
= MB
.substr(parentLineOffs
, i
-parentLineOffs
);
371 while (isWhitespace(MB
[i
]))
373 startSpace
= MB
.substr(startLineOffs
, i
-startLineOffs
);
375 if (parentSpace
.size() >= startSpace
.size())
377 if (!startSpace
.startswith(parentSpace
))
380 StringRef indent
= startSpace
.substr(parentSpace
.size());
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
];
387 while (isWhitespace(MB
[i
]))
389 StringRef origIndent
= MB
.substr(offs
, i
-offs
);
390 if (origIndent
.startswith(startSpace
))
391 RB
.InsertText(offs
, indent
, /*InsertAfter=*/false);
398 // A wrapper for a file stream that atomically overwrites the target.
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
{
406 AtomicallyMovedFile(DiagnosticsEngine
&Diagnostics
, StringRef Filename
,
408 : Diagnostics(Diagnostics
), Filename(Filename
), AllWritten(AllWritten
) {
409 TempFilename
= Filename
;
410 TempFilename
+= "-%%%%%%%%";
412 if (llvm::sys::fs::createUniqueFile(TempFilename
.str(), FD
, TempFilename
)) {
414 Diagnostics
.Report(clang::diag::err_unable_to_make_temp
)
417 FileStream
.reset(new llvm::raw_fd_ostream(FD
, /*shouldClose=*/true));
421 ~AtomicallyMovedFile() {
426 // Win32 does not allow rename/removing opened files.
429 if (std::error_code ec
=
430 llvm::sys::fs::rename(TempFilename
.str(), Filename
)) {
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
436 llvm::sys::fs::remove(TempFilename
.str());
440 bool ok() { return (bool)FileStream
; }
441 raw_ostream
&getStream() { return *FileStream
; }
444 DiagnosticsEngine
&Diagnostics
;
446 SmallString
<128> TempFilename
;
447 std::unique_ptr
<llvm::raw_fd_ostream
> FileStream
;
450 } // end anonymous namespace
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(),
460 I
->second
.write(File
.getStream());