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 "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" |
26 | #include <cassert> |
27 | #include <iterator> |
28 | #include <map> |
29 | #include <utility> |
30 | |
31 | using namespace clang; |
32 | using llvm::RewriteBuffer; |
33 | |
34 | //===----------------------------------------------------------------------===// |
35 | // Rewriter class |
36 | //===----------------------------------------------------------------------===// |
37 | |
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'; |
42 | } |
43 | |
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(Loc: Range.getBegin()) || |
49 | !isRewritable(Loc: Range.getEnd())) return -1; |
50 | |
51 | FileID StartFileID, EndFileID; |
52 | unsigned StartOff = getLocationOffsetAndFileID(Loc: Range.getBegin(), FID&: StartFileID); |
53 | unsigned EndOff = getLocationOffsetAndFileID(Loc: Range.getEnd(), FID&: EndFileID); |
54 | |
55 | if (StartFileID != EndFileID) |
56 | return -1; |
57 | |
58 | // If edits have been made to this buffer, the delta between the range may |
59 | // have changed. |
60 | std::map<FileID, RewriteBuffer>::const_iterator I = |
61 | RewriteBuffers.find(x: StartFileID); |
62 | if (I != RewriteBuffers.end()) { |
63 | const RewriteBuffer &RB = I->second; |
64 | EndOff = RB.getMappedOffset(OrigOffset: EndOff, AfterInserts: opts.IncludeInsertsAtEndOfRange); |
65 | StartOff = RB.getMappedOffset(OrigOffset: StartOff, AfterInserts: !opts.IncludeInsertsAtBeginOfRange); |
66 | } |
67 | |
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(Loc: Range.getEnd(), SM: *SourceMgr, LangOpts: *LangOpts); |
72 | |
73 | return EndOff-StartOff; |
74 | } |
75 | |
76 | int Rewriter::getRangeSize(SourceRange Range, RewriteOptions opts) const { |
77 | return getRangeSize(Range: CharSourceRange::getTokenRange(R: Range), opts); |
78 | } |
79 | |
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. |
83 | /// |
84 | /// Note that this method is not particularly efficient. |
85 | std::string Rewriter::getRewrittenText(CharSourceRange Range) const { |
86 | if (!isRewritable(Loc: Range.getBegin()) || |
87 | !isRewritable(Loc: Range.getEnd())) |
88 | return {}; |
89 | |
90 | FileID StartFileID, EndFileID; |
91 | unsigned StartOff, EndOff; |
92 | StartOff = getLocationOffsetAndFileID(Loc: Range.getBegin(), FID&: StartFileID); |
93 | EndOff = getLocationOffsetAndFileID(Loc: Range.getEnd(), FID&: EndFileID); |
94 | |
95 | if (StartFileID != EndFileID) |
96 | return {}; // Start and end in different buffers. |
97 | |
98 | // If edits have been made to this buffer, the delta between the range may |
99 | // have changed. |
100 | std::map<FileID, RewriteBuffer>::const_iterator I = |
101 | RewriteBuffers.find(x: 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(SL: Range.getBegin()); |
105 | |
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()) |
109 | EndOff += |
110 | Lexer::MeasureTokenLength(Loc: Range.getEnd(), SM: *SourceMgr, LangOpts: *LangOpts); |
111 | return std::string(Ptr, Ptr+EndOff-StartOff); |
112 | } |
113 | |
114 | const RewriteBuffer &RB = I->second; |
115 | EndOff = RB.getMappedOffset(OrigOffset: EndOff, AfterInserts: true); |
116 | StartOff = RB.getMappedOffset(OrigOffset: StartOff); |
117 | |
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(Loc: Range.getEnd(), SM: *SourceMgr, LangOpts: *LangOpts); |
122 | |
123 | // Advance the iterators to the right spot, yay for linear time algorithms. |
124 | RewriteBuffer::iterator Start = RB.begin(); |
125 | std::advance(i&: Start, n: StartOff); |
126 | RewriteBuffer::iterator End = Start; |
127 | assert(EndOff >= StartOff && "Invalid iteration distance" ); |
128 | std::advance(i&: End, n: EndOff-StartOff); |
129 | |
130 | return std::string(Start, End); |
131 | } |
132 | |
133 | unsigned Rewriter::getLocationOffsetAndFileID(SourceLocation Loc, |
134 | FileID &FID) const { |
135 | assert(Loc.isValid() && "Invalid location" ); |
136 | std::pair<FileID, unsigned> V = SourceMgr->getDecomposedLoc(Loc); |
137 | FID = V.first; |
138 | return V.second; |
139 | } |
140 | |
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(x: FID); |
145 | if (I != RewriteBuffers.end() && I->first == FID) |
146 | return I->second; |
147 | I = RewriteBuffers.insert(position: I, x: std::make_pair(x&: FID, y: RewriteBuffer())); |
148 | |
149 | StringRef MB = SourceMgr->getBufferData(FID); |
150 | I->second.Initialize(BufStart: MB.begin(), BufEnd: MB.end()); |
151 | |
152 | return I->second; |
153 | } |
154 | |
155 | /// InsertText - Insert the specified string at the specified location in the |
156 | /// original buffer. |
157 | bool Rewriter::InsertText(SourceLocation Loc, StringRef Str, |
158 | bool InsertAfter, bool indentNewLines) { |
159 | if (!isRewritable(Loc)) return true; |
160 | FileID FID; |
161 | unsigned StartOffs = getLocationOffsetAndFileID(Loc, FID); |
162 | |
163 | SmallString<128> indentedStr; |
164 | if (indentNewLines && Str.contains(C: '\n')) { |
165 | StringRef MB = SourceMgr->getBufferData(FID); |
166 | |
167 | unsigned lineNo = SourceMgr->getLineNumber(FID, FilePos: StartOffs) - 1; |
168 | const SrcMgr::ContentCache *Content = |
169 | &SourceMgr->getSLocEntry(FID).getFile().getContentCache(); |
170 | unsigned lineOffs = Content->SourceLineCache[lineNo]; |
171 | |
172 | // Find the whitespace at the start of the line. |
173 | StringRef indentSpace; |
174 | { |
175 | unsigned i = lineOffs; |
176 | while (isWhitespaceExceptNL(c: MB[i])) |
177 | ++i; |
178 | indentSpace = MB.substr(Start: lineOffs, N: i-lineOffs); |
179 | } |
180 | |
181 | SmallVector<StringRef, 4> lines; |
182 | Str.split(A&: lines, Separator: "\n" ); |
183 | |
184 | for (unsigned i = 0, e = lines.size(); i != e; ++i) { |
185 | indentedStr += lines[i]; |
186 | if (i < e-1) { |
187 | indentedStr += '\n'; |
188 | indentedStr += indentSpace; |
189 | } |
190 | } |
191 | Str = indentedStr.str(); |
192 | } |
193 | |
194 | getEditBuffer(FID).InsertText(OrigOffset: StartOffs, Str, InsertAfter); |
195 | return false; |
196 | } |
197 | |
198 | bool Rewriter::InsertTextAfterToken(SourceLocation Loc, StringRef Str) { |
199 | if (!isRewritable(Loc)) return true; |
200 | FileID FID; |
201 | unsigned StartOffs = getLocationOffsetAndFileID(Loc, FID); |
202 | RewriteOptions rangeOpts; |
203 | rangeOpts.IncludeInsertsAtBeginOfRange = false; |
204 | StartOffs += getRangeSize(Range: SourceRange(Loc, Loc), opts: rangeOpts); |
205 | getEditBuffer(FID).InsertText(OrigOffset: StartOffs, Str, /*InsertAfter*/true); |
206 | return false; |
207 | } |
208 | |
209 | /// RemoveText - Remove the specified text region. |
210 | bool Rewriter::RemoveText(SourceLocation Start, unsigned Length, |
211 | RewriteOptions opts) { |
212 | if (!isRewritable(Loc: Start)) return true; |
213 | FileID FID; |
214 | unsigned StartOffs = getLocationOffsetAndFileID(Loc: Start, FID); |
215 | getEditBuffer(FID).RemoveText(OrigOffset: StartOffs, Size: Length, removeLineIfEmpty: opts.RemoveLineIfEmpty); |
216 | return false; |
217 | } |
218 | |
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" |
221 | /// operation. |
222 | bool Rewriter::ReplaceText(SourceLocation Start, unsigned OrigLength, |
223 | StringRef NewStr) { |
224 | if (!isRewritable(Loc: Start)) return true; |
225 | FileID StartFileID; |
226 | unsigned StartOffs = getLocationOffsetAndFileID(Loc: Start, FID&: StartFileID); |
227 | |
228 | getEditBuffer(FID: StartFileID).ReplaceText(OrigOffset: StartOffs, OrigLength, NewStr); |
229 | return false; |
230 | } |
231 | |
232 | bool Rewriter::ReplaceText(SourceRange range, SourceRange replacementRange) { |
233 | if (!isRewritable(Loc: range.getBegin())) return true; |
234 | if (!isRewritable(Loc: range.getEnd())) return true; |
235 | if (replacementRange.isInvalid()) return true; |
236 | SourceLocation start = range.getBegin(); |
237 | unsigned origLength = getRangeSize(Range: range); |
238 | unsigned newLength = getRangeSize(Range: replacementRange); |
239 | FileID FID; |
240 | unsigned newOffs = getLocationOffsetAndFileID(Loc: replacementRange.getBegin(), |
241 | FID); |
242 | StringRef MB = SourceMgr->getBufferData(FID); |
243 | return ReplaceText(Start: start, OrigLength: origLength, NewStr: MB.substr(Start: newOffs, N: newLength)); |
244 | } |
245 | |
246 | bool Rewriter::IncreaseIndentation(CharSourceRange range, |
247 | SourceLocation parentIndent) { |
248 | if (range.isInvalid()) return true; |
249 | if (!isRewritable(Loc: range.getBegin())) return true; |
250 | if (!isRewritable(Loc: range.getEnd())) return true; |
251 | if (!isRewritable(Loc: parentIndent)) return true; |
252 | |
253 | FileID StartFileID, EndFileID, parentFileID; |
254 | unsigned StartOff, EndOff, parentOff; |
255 | |
256 | StartOff = getLocationOffsetAndFileID(Loc: range.getBegin(), FID&: StartFileID); |
257 | EndOff = getLocationOffsetAndFileID(Loc: range.getEnd(), FID&: EndFileID); |
258 | parentOff = getLocationOffsetAndFileID(Loc: parentIndent, FID&: parentFileID); |
259 | |
260 | if (StartFileID != EndFileID || StartFileID != parentFileID) |
261 | return true; |
262 | if (StartOff > EndOff) |
263 | return true; |
264 | |
265 | FileID FID = StartFileID; |
266 | StringRef MB = SourceMgr->getBufferData(FID); |
267 | |
268 | unsigned parentLineNo = SourceMgr->getLineNumber(FID, FilePos: parentOff) - 1; |
269 | unsigned startLineNo = SourceMgr->getLineNumber(FID, FilePos: StartOff) - 1; |
270 | unsigned endLineNo = SourceMgr->getLineNumber(FID, FilePos: EndOff) - 1; |
271 | |
272 | const SrcMgr::ContentCache *Content = |
273 | &SourceMgr->getSLocEntry(FID).getFile().getContentCache(); |
274 | |
275 | // Find where the lines start. |
276 | unsigned parentLineOffs = Content->SourceLineCache[parentLineNo]; |
277 | unsigned startLineOffs = Content->SourceLineCache[startLineNo]; |
278 | |
279 | // Find the whitespace at the start of each line. |
280 | StringRef parentSpace, startSpace; |
281 | { |
282 | unsigned i = parentLineOffs; |
283 | while (isWhitespaceExceptNL(c: MB[i])) |
284 | ++i; |
285 | parentSpace = MB.substr(Start: parentLineOffs, N: i-parentLineOffs); |
286 | |
287 | i = startLineOffs; |
288 | while (isWhitespaceExceptNL(c: MB[i])) |
289 | ++i; |
290 | startSpace = MB.substr(Start: startLineOffs, N: i-startLineOffs); |
291 | } |
292 | if (parentSpace.size() >= startSpace.size()) |
293 | return true; |
294 | if (!startSpace.starts_with(Prefix: parentSpace)) |
295 | return true; |
296 | |
297 | StringRef indent = startSpace.substr(Start: parentSpace.size()); |
298 | |
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]; |
303 | unsigned i = offs; |
304 | while (isWhitespaceExceptNL(c: MB[i])) |
305 | ++i; |
306 | StringRef origIndent = MB.substr(Start: offs, N: i-offs); |
307 | if (origIndent.starts_with(Prefix: startSpace)) |
308 | RB.InsertText(OrigOffset: offs, Str: indent, /*InsertAfter=*/false); |
309 | } |
310 | |
311 | return false; |
312 | } |
313 | |
314 | bool Rewriter::overwriteChangedFiles() { |
315 | bool AllWritten = true; |
316 | auto& Diag = getSourceMgr().getDiagnostics(); |
317 | unsigned OverwriteFailure = Diag.getCustomDiagID( |
318 | L: DiagnosticsEngine::Error, FormatString: "unable to overwrite file %0: %1" ); |
319 | for (buffer_iterator I = buffer_begin(), E = buffer_end(); I != E; ++I) { |
320 | OptionalFileEntryRef Entry = getSourceMgr().getFileEntryRefForID(FID: I->first); |
321 | llvm::SmallString<128> Path(Entry->getName()); |
322 | getSourceMgr().getFileManager().makeAbsolutePath(Path); |
323 | if (auto Error = llvm::writeToOutput(OutputFileName: Path, Write: [&](llvm::raw_ostream &OS) { |
324 | I->second.write(Stream&: OS); |
325 | return llvm::Error::success(); |
326 | })) { |
327 | Diag.Report(DiagID: OverwriteFailure) |
328 | << Entry->getName() << llvm::toString(E: std::move(Error)); |
329 | AllWritten = false; |
330 | } |
331 | } |
332 | return !AllWritten; |
333 | } |
334 | |