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