| 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 | FileIDAndOffset 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 | |