1 //===- Rewriter.cpp - Code rewriting interface ----------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 // This file defines the Rewriter class, which is used for code
12 //===----------------------------------------------------------------------===//
14 #include "clang/Rewrite/Core/Rewriter.h"
15 #include "clang/Basic/Diagnostic.h"
16 #include "clang/Basic/DiagnosticIDs.h"
17 #include "clang/Basic/SourceLocation.h"
18 #include "clang/Basic/SourceManager.h"
19 #include "clang/Lex/Lexer.h"
20 #include "llvm/ADT/RewriteBuffer.h"
21 #include "llvm/ADT/RewriteRope.h"
22 #include "llvm/ADT/SmallVector.h"
23 #include "llvm/ADT/StringRef.h"
24 #include "llvm/Support/Error.h"
25 #include "llvm/Support/raw_ostream.h"
31 using namespace clang
;
32 using llvm::RewriteBuffer
;
34 //===----------------------------------------------------------------------===//
36 //===----------------------------------------------------------------------===//
38 /// Return true if this character is non-new-line whitespace:
39 /// ' ', '\\t', '\\f', '\\v', '\\r'.
40 static inline bool isWhitespaceExceptNL(unsigned char c
) {
41 return c
== ' ' || c
== '\t' || c
== '\f' || c
== '\v' || c
== '\r';
44 /// getRangeSize - Return the size in bytes of the specified range if they
45 /// are in the same file. If not, this returns -1.
46 int Rewriter::getRangeSize(const CharSourceRange
&Range
,
47 RewriteOptions opts
) const {
48 if (!isRewritable(Range
.getBegin()) ||
49 !isRewritable(Range
.getEnd())) return -1;
51 FileID StartFileID
, EndFileID
;
52 unsigned StartOff
= getLocationOffsetAndFileID(Range
.getBegin(), StartFileID
);
53 unsigned EndOff
= getLocationOffsetAndFileID(Range
.getEnd(), EndFileID
);
55 if (StartFileID
!= EndFileID
)
58 // If edits have been made to this buffer, the delta between the range may
60 std::map
<FileID
, RewriteBuffer
>::const_iterator I
=
61 RewriteBuffers
.find(StartFileID
);
62 if (I
!= RewriteBuffers
.end()) {
63 const RewriteBuffer
&RB
= I
->second
;
64 EndOff
= RB
.getMappedOffset(EndOff
, opts
.IncludeInsertsAtEndOfRange
);
65 StartOff
= RB
.getMappedOffset(StartOff
, !opts
.IncludeInsertsAtBeginOfRange
);
68 // Adjust the end offset to the end of the last token, instead of being the
69 // start of the last token if this is a token range.
70 if (Range
.isTokenRange())
71 EndOff
+= Lexer::MeasureTokenLength(Range
.getEnd(), *SourceMgr
, *LangOpts
);
73 return EndOff
-StartOff
;
76 int Rewriter::getRangeSize(SourceRange Range
, RewriteOptions opts
) const {
77 return getRangeSize(CharSourceRange::getTokenRange(Range
), opts
);
80 /// getRewrittenText - Return the rewritten form of the text in the specified
81 /// range. If the start or end of the range was unrewritable or if they are
82 /// in different buffers, this returns an empty string.
84 /// Note that this method is not particularly efficient.
85 std::string
Rewriter::getRewrittenText(CharSourceRange Range
) const {
86 if (!isRewritable(Range
.getBegin()) ||
87 !isRewritable(Range
.getEnd()))
90 FileID StartFileID
, EndFileID
;
91 unsigned StartOff
, EndOff
;
92 StartOff
= getLocationOffsetAndFileID(Range
.getBegin(), StartFileID
);
93 EndOff
= getLocationOffsetAndFileID(Range
.getEnd(), EndFileID
);
95 if (StartFileID
!= EndFileID
)
96 return {}; // Start and end in different buffers.
98 // If edits have been made to this buffer, the delta between the range may
100 std::map
<FileID
, RewriteBuffer
>::const_iterator I
=
101 RewriteBuffers
.find(StartFileID
);
102 if (I
== RewriteBuffers
.end()) {
103 // If the buffer hasn't been rewritten, just return the text from the input.
104 const char *Ptr
= SourceMgr
->getCharacterData(Range
.getBegin());
106 // Adjust the end offset to the end of the last token, instead of being the
107 // start of the last token.
108 if (Range
.isTokenRange())
110 Lexer::MeasureTokenLength(Range
.getEnd(), *SourceMgr
, *LangOpts
);
111 return std::string(Ptr
, Ptr
+EndOff
-StartOff
);
114 const RewriteBuffer
&RB
= I
->second
;
115 EndOff
= RB
.getMappedOffset(EndOff
, true);
116 StartOff
= RB
.getMappedOffset(StartOff
);
118 // Adjust the end offset to the end of the last token, instead of being the
119 // start of the last token.
120 if (Range
.isTokenRange())
121 EndOff
+= Lexer::MeasureTokenLength(Range
.getEnd(), *SourceMgr
, *LangOpts
);
123 // Advance the iterators to the right spot, yay for linear time algorithms.
124 RewriteBuffer::iterator Start
= RB
.begin();
125 std::advance(Start
, StartOff
);
126 RewriteBuffer::iterator End
= Start
;
127 assert(EndOff
>= StartOff
&& "Invalid iteration distance");
128 std::advance(End
, EndOff
-StartOff
);
130 return std::string(Start
, End
);
133 unsigned Rewriter::getLocationOffsetAndFileID(SourceLocation Loc
,
135 assert(Loc
.isValid() && "Invalid location");
136 std::pair
<FileID
, unsigned> V
= SourceMgr
->getDecomposedLoc(Loc
);
141 /// getEditBuffer - Get or create a RewriteBuffer for the specified FileID.
142 RewriteBuffer
&Rewriter::getEditBuffer(FileID FID
) {
143 std::map
<FileID
, RewriteBuffer
>::iterator I
=
144 RewriteBuffers
.lower_bound(FID
);
145 if (I
!= RewriteBuffers
.end() && I
->first
== FID
)
147 I
= RewriteBuffers
.insert(I
, std::make_pair(FID
, RewriteBuffer()));
149 StringRef MB
= SourceMgr
->getBufferData(FID
);
150 I
->second
.Initialize(MB
.begin(), MB
.end());
155 /// InsertText - Insert the specified string at the specified location in the
157 bool Rewriter::InsertText(SourceLocation Loc
, StringRef Str
,
158 bool InsertAfter
, bool indentNewLines
) {
159 if (!isRewritable(Loc
)) return true;
161 unsigned StartOffs
= getLocationOffsetAndFileID(Loc
, FID
);
163 SmallString
<128> indentedStr
;
164 if (indentNewLines
&& Str
.contains('\n')) {
165 StringRef MB
= SourceMgr
->getBufferData(FID
);
167 unsigned lineNo
= SourceMgr
->getLineNumber(FID
, StartOffs
) - 1;
168 const SrcMgr::ContentCache
*Content
=
169 &SourceMgr
->getSLocEntry(FID
).getFile().getContentCache();
170 unsigned lineOffs
= Content
->SourceLineCache
[lineNo
];
172 // Find the whitespace at the start of the line.
173 StringRef indentSpace
;
175 unsigned i
= lineOffs
;
176 while (isWhitespaceExceptNL(MB
[i
]))
178 indentSpace
= MB
.substr(lineOffs
, i
-lineOffs
);
181 SmallVector
<StringRef
, 4> lines
;
182 Str
.split(lines
, "\n");
184 for (unsigned i
= 0, e
= lines
.size(); i
!= e
; ++i
) {
185 indentedStr
+= lines
[i
];
188 indentedStr
+= indentSpace
;
191 Str
= indentedStr
.str();
194 getEditBuffer(FID
).InsertText(StartOffs
, Str
, InsertAfter
);
198 bool Rewriter::InsertTextAfterToken(SourceLocation Loc
, StringRef Str
) {
199 if (!isRewritable(Loc
)) return true;
201 unsigned StartOffs
= getLocationOffsetAndFileID(Loc
, FID
);
202 RewriteOptions rangeOpts
;
203 rangeOpts
.IncludeInsertsAtBeginOfRange
= false;
204 StartOffs
+= getRangeSize(SourceRange(Loc
, Loc
), rangeOpts
);
205 getEditBuffer(FID
).InsertText(StartOffs
, Str
, /*InsertAfter*/true);
209 /// RemoveText - Remove the specified text region.
210 bool Rewriter::RemoveText(SourceLocation Start
, unsigned Length
,
211 RewriteOptions opts
) {
212 if (!isRewritable(Start
)) return true;
214 unsigned StartOffs
= getLocationOffsetAndFileID(Start
, FID
);
215 getEditBuffer(FID
).RemoveText(StartOffs
, Length
, opts
.RemoveLineIfEmpty
);
219 /// ReplaceText - This method replaces a range of characters in the input
220 /// buffer with a new string. This is effectively a combined "remove/insert"
222 bool Rewriter::ReplaceText(SourceLocation Start
, unsigned OrigLength
,
224 if (!isRewritable(Start
)) return true;
226 unsigned StartOffs
= getLocationOffsetAndFileID(Start
, StartFileID
);
228 getEditBuffer(StartFileID
).ReplaceText(StartOffs
, OrigLength
, NewStr
);
232 bool Rewriter::ReplaceText(SourceRange range
, SourceRange replacementRange
) {
233 if (!isRewritable(range
.getBegin())) return true;
234 if (!isRewritable(range
.getEnd())) return true;
235 if (replacementRange
.isInvalid()) return true;
236 SourceLocation start
= range
.getBegin();
237 unsigned origLength
= getRangeSize(range
);
238 unsigned newLength
= getRangeSize(replacementRange
);
240 unsigned newOffs
= getLocationOffsetAndFileID(replacementRange
.getBegin(),
242 StringRef MB
= SourceMgr
->getBufferData(FID
);
243 return ReplaceText(start
, origLength
, MB
.substr(newOffs
, newLength
));
246 bool Rewriter::IncreaseIndentation(CharSourceRange range
,
247 SourceLocation parentIndent
) {
248 if (range
.isInvalid()) return true;
249 if (!isRewritable(range
.getBegin())) return true;
250 if (!isRewritable(range
.getEnd())) return true;
251 if (!isRewritable(parentIndent
)) return true;
253 FileID StartFileID
, EndFileID
, parentFileID
;
254 unsigned StartOff
, EndOff
, parentOff
;
256 StartOff
= getLocationOffsetAndFileID(range
.getBegin(), StartFileID
);
257 EndOff
= getLocationOffsetAndFileID(range
.getEnd(), EndFileID
);
258 parentOff
= getLocationOffsetAndFileID(parentIndent
, parentFileID
);
260 if (StartFileID
!= EndFileID
|| StartFileID
!= parentFileID
)
262 if (StartOff
> EndOff
)
265 FileID FID
= StartFileID
;
266 StringRef MB
= SourceMgr
->getBufferData(FID
);
268 unsigned parentLineNo
= SourceMgr
->getLineNumber(FID
, parentOff
) - 1;
269 unsigned startLineNo
= SourceMgr
->getLineNumber(FID
, StartOff
) - 1;
270 unsigned endLineNo
= SourceMgr
->getLineNumber(FID
, EndOff
) - 1;
272 const SrcMgr::ContentCache
*Content
=
273 &SourceMgr
->getSLocEntry(FID
).getFile().getContentCache();
275 // Find where the lines start.
276 unsigned parentLineOffs
= Content
->SourceLineCache
[parentLineNo
];
277 unsigned startLineOffs
= Content
->SourceLineCache
[startLineNo
];
279 // Find the whitespace at the start of each line.
280 StringRef parentSpace
, startSpace
;
282 unsigned i
= parentLineOffs
;
283 while (isWhitespaceExceptNL(MB
[i
]))
285 parentSpace
= MB
.substr(parentLineOffs
, i
-parentLineOffs
);
288 while (isWhitespaceExceptNL(MB
[i
]))
290 startSpace
= MB
.substr(startLineOffs
, i
-startLineOffs
);
292 if (parentSpace
.size() >= startSpace
.size())
294 if (!startSpace
.starts_with(parentSpace
))
297 StringRef indent
= startSpace
.substr(parentSpace
.size());
299 // Indent the lines between start/end offsets.
300 RewriteBuffer
&RB
= getEditBuffer(FID
);
301 for (unsigned lineNo
= startLineNo
; lineNo
<= endLineNo
; ++lineNo
) {
302 unsigned offs
= Content
->SourceLineCache
[lineNo
];
304 while (isWhitespaceExceptNL(MB
[i
]))
306 StringRef origIndent
= MB
.substr(offs
, i
-offs
);
307 if (origIndent
.starts_with(startSpace
))
308 RB
.InsertText(offs
, indent
, /*InsertAfter=*/false);
314 bool Rewriter::overwriteChangedFiles() {
315 bool AllWritten
= true;
316 auto& Diag
= getSourceMgr().getDiagnostics();
317 unsigned OverwriteFailure
= Diag
.getCustomDiagID(
318 DiagnosticsEngine::Error
, "unable to overwrite file %0: %1");
319 for (buffer_iterator I
= buffer_begin(), E
= buffer_end(); I
!= E
; ++I
) {
320 OptionalFileEntryRef Entry
= getSourceMgr().getFileEntryRefForID(I
->first
);
321 llvm::SmallString
<128> Path(Entry
->getName());
322 getSourceMgr().getFileManager().makeAbsolutePath(Path
);
323 if (auto Error
= llvm::writeToOutput(Path
, [&](llvm::raw_ostream
&OS
) {
325 return llvm::Error::success();
327 Diag
.Report(OverwriteFailure
)
328 << Entry
->getName() << llvm::toString(std::move(Error
));