334 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// This file defines the Rewriter class, which is used for code10// 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 31using namespace clang;32using llvm::RewriteBuffer;33 34//===----------------------------------------------------------------------===//35// Rewriter class36//===----------------------------------------------------------------------===//37 38/// Return true if this character is non-new-line whitespace:39/// ' ', '\\t', '\\f', '\\v', '\\r'.40static 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 they45/// are in the same file. If not, this returns -1.46int Rewriter::getRangeSize(const CharSourceRange &Range,47 RewriteOptions opts) const {48 if (!isRewritable(Range.getBegin()) ||49 !isRewritable(Range.getEnd())) return -1;50 51 FileID StartFileID, EndFileID;52 unsigned StartOff = getLocationOffsetAndFileID(Range.getBegin(), StartFileID);53 unsigned EndOff = getLocationOffsetAndFileID(Range.getEnd(), EndFileID);54 55 if (StartFileID != EndFileID)56 return -1;57 58 // If edits have been made to this buffer, the delta between the range may59 // have changed.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);66 }67 68 // Adjust the end offset to the end of the last token, instead of being the69 // start of the last token if this is a token range.70 if (Range.isTokenRange())71 EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts);72 73 return EndOff-StartOff;74}75 76int Rewriter::getRangeSize(SourceRange Range, RewriteOptions opts) const {77 return getRangeSize(CharSourceRange::getTokenRange(Range), opts);78}79 80/// getRewrittenText - Return the rewritten form of the text in the specified81/// range. If the start or end of the range was unrewritable or if they are82/// in different buffers, this returns an empty string.83///84/// Note that this method is not particularly efficient.85std::string Rewriter::getRewrittenText(CharSourceRange Range) const {86 if (!isRewritable(Range.getBegin()) ||87 !isRewritable(Range.getEnd()))88 return {};89 90 FileID StartFileID, EndFileID;91 unsigned StartOff, EndOff;92 StartOff = getLocationOffsetAndFileID(Range.getBegin(), StartFileID);93 EndOff = getLocationOffsetAndFileID(Range.getEnd(), 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 may99 // have changed.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());105 106 // Adjust the end offset to the end of the last token, instead of being the107 // start of the last token.108 if (Range.isTokenRange())109 EndOff +=110 Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts);111 return std::string(Ptr, Ptr+EndOff-StartOff);112 }113 114 const RewriteBuffer &RB = I->second;115 EndOff = RB.getMappedOffset(EndOff, true);116 StartOff = RB.getMappedOffset(StartOff);117 118 // Adjust the end offset to the end of the last token, instead of being the119 // start of the last token.120 if (Range.isTokenRange())121 EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts);122 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);129 130 return std::string(Start, End);131}132 133unsigned 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.142RewriteBuffer &Rewriter::getEditBuffer(FileID FID) {143 std::map<FileID, RewriteBuffer>::iterator I =144 RewriteBuffers.lower_bound(FID);145 if (I != RewriteBuffers.end() && I->first == FID)146 return I->second;147 I = RewriteBuffers.insert(I, std::make_pair(FID, RewriteBuffer()));148 149 StringRef MB = SourceMgr->getBufferData(FID);150 I->second.Initialize(MB.begin(), MB.end());151 152 return I->second;153}154 155/// InsertText - Insert the specified string at the specified location in the156/// original buffer.157bool 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('\n')) {165 StringRef MB = SourceMgr->getBufferData(FID);166 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];171 172 // Find the whitespace at the start of the line.173 StringRef indentSpace;174 {175 unsigned i = lineOffs;176 while (isWhitespaceExceptNL(MB[i]))177 ++i;178 indentSpace = MB.substr(lineOffs, i-lineOffs);179 }180 181 SmallVector<StringRef, 4> lines;182 Str.split(lines, "\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(StartOffs, Str, InsertAfter);195 return false;196}197 198bool 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(SourceRange(Loc, Loc), rangeOpts);205 getEditBuffer(FID).InsertText(StartOffs, Str, /*InsertAfter*/true);206 return false;207}208 209/// RemoveText - Remove the specified text region.210bool Rewriter::RemoveText(SourceLocation Start, unsigned Length,211 RewriteOptions opts) {212 if (!isRewritable(Start)) return true;213 FileID FID;214 unsigned StartOffs = getLocationOffsetAndFileID(Start, FID);215 getEditBuffer(FID).RemoveText(StartOffs, Length, opts.RemoveLineIfEmpty);216 return false;217}218 219/// ReplaceText - This method replaces a range of characters in the input220/// buffer with a new string. This is effectively a combined "remove/insert"221/// operation.222bool Rewriter::ReplaceText(SourceLocation Start, unsigned OrigLength,223 StringRef NewStr) {224 if (!isRewritable(Start)) return true;225 FileID StartFileID;226 unsigned StartOffs = getLocationOffsetAndFileID(Start, StartFileID);227 228 getEditBuffer(StartFileID).ReplaceText(StartOffs, OrigLength, NewStr);229 return false;230}231 232bool 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);239 FileID FID;240 unsigned newOffs = getLocationOffsetAndFileID(replacementRange.getBegin(),241 FID);242 StringRef MB = SourceMgr->getBufferData(FID);243 return ReplaceText(start, origLength, MB.substr(newOffs, newLength));244}245 246bool 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;252 253 FileID StartFileID, EndFileID, parentFileID;254 unsigned StartOff, EndOff, parentOff;255 256 StartOff = getLocationOffsetAndFileID(range.getBegin(), StartFileID);257 EndOff = getLocationOffsetAndFileID(range.getEnd(), EndFileID);258 parentOff = getLocationOffsetAndFileID(parentIndent, 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, parentOff) - 1;269 unsigned startLineNo = SourceMgr->getLineNumber(FID, StartOff) - 1;270 unsigned endLineNo = SourceMgr->getLineNumber(FID, 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(MB[i]))284 ++i;285 parentSpace = MB.substr(parentLineOffs, i-parentLineOffs);286 287 i = startLineOffs;288 while (isWhitespaceExceptNL(MB[i]))289 ++i;290 startSpace = MB.substr(startLineOffs, i-startLineOffs);291 }292 if (parentSpace.size() >= startSpace.size())293 return true;294 if (!startSpace.starts_with(parentSpace))295 return true;296 297 StringRef indent = startSpace.substr(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(MB[i]))305 ++i;306 StringRef origIndent = MB.substr(offs, i-offs);307 if (origIndent.starts_with(startSpace))308 RB.InsertText(offs, indent, /*InsertAfter=*/false);309 }310 311 return false;312}313 314bool 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) {324 I->second.write(OS);325 return llvm::Error::success();326 })) {327 Diag.Report(OverwriteFailure)328 << Entry->getName() << llvm::toString(std::move(Error));329 AllWritten = false;330 }331 }332 return !AllWritten;333}334