Path: blob/main/contrib/llvm-project/clang/lib/Rewrite/Rewriter.cpp
35233 views
//===- Rewriter.cpp - Code rewriting interface ----------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This file defines the Rewriter class, which is used for code9// transformations.10//11//===----------------------------------------------------------------------===//1213#include "clang/Rewrite/Core/Rewriter.h"14#include "clang/Basic/Diagnostic.h"15#include "clang/Basic/DiagnosticIDs.h"16#include "clang/Basic/SourceLocation.h"17#include "clang/Basic/SourceManager.h"18#include "clang/Lex/Lexer.h"19#include "clang/Rewrite/Core/RewriteBuffer.h"20#include "clang/Rewrite/Core/RewriteRope.h"21#include "llvm/ADT/SmallVector.h"22#include "llvm/ADT/StringRef.h"23#include "llvm/Support/Error.h"24#include "llvm/Support/raw_ostream.h"25#include <cassert>26#include <iterator>27#include <map>28#include <utility>2930using namespace clang;3132raw_ostream &RewriteBuffer::write(raw_ostream &os) const {33// Walk RewriteRope chunks efficiently using MoveToNextPiece() instead of the34// character iterator.35for (RopePieceBTreeIterator I = begin(), E = end(); I != E;36I.MoveToNextPiece())37os << I.piece();38return os;39}4041/// Return true if this character is non-new-line whitespace:42/// ' ', '\\t', '\\f', '\\v', '\\r'.43static inline bool isWhitespaceExceptNL(unsigned char c) {44switch (c) {45case ' ':46case '\t':47case '\f':48case '\v':49case '\r':50return true;51default:52return false;53}54}5556void RewriteBuffer::RemoveText(unsigned OrigOffset, unsigned Size,57bool removeLineIfEmpty) {58// Nothing to remove, exit early.59if (Size == 0) return;6061unsigned RealOffset = getMappedOffset(OrigOffset, true);62assert(RealOffset+Size <= Buffer.size() && "Invalid location");6364// Remove the dead characters.65Buffer.erase(RealOffset, Size);6667// Add a delta so that future changes are offset correctly.68AddReplaceDelta(OrigOffset, -Size);6970if (removeLineIfEmpty) {71// Find the line that the remove occurred and if it is completely empty72// remove the line as well.7374iterator curLineStart = begin();75unsigned curLineStartOffs = 0;76iterator posI = begin();77for (unsigned i = 0; i != RealOffset; ++i) {78if (*posI == '\n') {79curLineStart = posI;80++curLineStart;81curLineStartOffs = i + 1;82}83++posI;84}8586unsigned lineSize = 0;87posI = curLineStart;88while (posI != end() && isWhitespaceExceptNL(*posI)) {89++posI;90++lineSize;91}92if (posI != end() && *posI == '\n') {93Buffer.erase(curLineStartOffs, lineSize + 1/* + '\n'*/);94// FIXME: Here, the offset of the start of the line is supposed to be95// expressed in terms of the original input not the "real" rewrite96// buffer. How do we compute that reliably? It might be tempting to use97// curLineStartOffs + OrigOffset - RealOffset, but that assumes the98// difference between the original and real offset is the same at the99// removed text and at the start of the line, but that's not true if100// edits were previously made earlier on the line. This bug is also101// documented by a FIXME on the definition of102// clang::Rewriter::RewriteOptions::RemoveLineIfEmpty. A reproducer for103// the implementation below is the test RemoveLineIfEmpty in104// clang/unittests/Rewrite/RewriteBufferTest.cpp.105AddReplaceDelta(curLineStartOffs, -(lineSize + 1/* + '\n'*/));106}107}108}109110void RewriteBuffer::InsertText(unsigned OrigOffset, StringRef Str,111bool InsertAfter) {112// Nothing to insert, exit early.113if (Str.empty()) return;114115unsigned RealOffset = getMappedOffset(OrigOffset, InsertAfter);116Buffer.insert(RealOffset, Str.begin(), Str.end());117118// Add a delta so that future changes are offset correctly.119AddInsertDelta(OrigOffset, Str.size());120}121122/// ReplaceText - This method replaces a range of characters in the input123/// buffer with a new string. This is effectively a combined "remove+insert"124/// operation.125void RewriteBuffer::ReplaceText(unsigned OrigOffset, unsigned OrigLength,126StringRef NewStr) {127unsigned RealOffset = getMappedOffset(OrigOffset, true);128Buffer.erase(RealOffset, OrigLength);129Buffer.insert(RealOffset, NewStr.begin(), NewStr.end());130if (OrigLength != NewStr.size())131AddReplaceDelta(OrigOffset, NewStr.size() - OrigLength);132}133134//===----------------------------------------------------------------------===//135// Rewriter class136//===----------------------------------------------------------------------===//137138/// getRangeSize - Return the size in bytes of the specified range if they139/// are in the same file. If not, this returns -1.140int Rewriter::getRangeSize(const CharSourceRange &Range,141RewriteOptions opts) const {142if (!isRewritable(Range.getBegin()) ||143!isRewritable(Range.getEnd())) return -1;144145FileID StartFileID, EndFileID;146unsigned StartOff = getLocationOffsetAndFileID(Range.getBegin(), StartFileID);147unsigned EndOff = getLocationOffsetAndFileID(Range.getEnd(), EndFileID);148149if (StartFileID != EndFileID)150return -1;151152// If edits have been made to this buffer, the delta between the range may153// have changed.154std::map<FileID, RewriteBuffer>::const_iterator I =155RewriteBuffers.find(StartFileID);156if (I != RewriteBuffers.end()) {157const RewriteBuffer &RB = I->second;158EndOff = RB.getMappedOffset(EndOff, opts.IncludeInsertsAtEndOfRange);159StartOff = RB.getMappedOffset(StartOff, !opts.IncludeInsertsAtBeginOfRange);160}161162// Adjust the end offset to the end of the last token, instead of being the163// start of the last token if this is a token range.164if (Range.isTokenRange())165EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts);166167return EndOff-StartOff;168}169170int Rewriter::getRangeSize(SourceRange Range, RewriteOptions opts) const {171return getRangeSize(CharSourceRange::getTokenRange(Range), opts);172}173174/// getRewrittenText - Return the rewritten form of the text in the specified175/// range. If the start or end of the range was unrewritable or if they are176/// in different buffers, this returns an empty string.177///178/// Note that this method is not particularly efficient.179std::string Rewriter::getRewrittenText(CharSourceRange Range) const {180if (!isRewritable(Range.getBegin()) ||181!isRewritable(Range.getEnd()))182return {};183184FileID StartFileID, EndFileID;185unsigned StartOff, EndOff;186StartOff = getLocationOffsetAndFileID(Range.getBegin(), StartFileID);187EndOff = getLocationOffsetAndFileID(Range.getEnd(), EndFileID);188189if (StartFileID != EndFileID)190return {}; // Start and end in different buffers.191192// If edits have been made to this buffer, the delta between the range may193// have changed.194std::map<FileID, RewriteBuffer>::const_iterator I =195RewriteBuffers.find(StartFileID);196if (I == RewriteBuffers.end()) {197// If the buffer hasn't been rewritten, just return the text from the input.198const char *Ptr = SourceMgr->getCharacterData(Range.getBegin());199200// Adjust the end offset to the end of the last token, instead of being the201// start of the last token.202if (Range.isTokenRange())203EndOff +=204Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts);205return std::string(Ptr, Ptr+EndOff-StartOff);206}207208const RewriteBuffer &RB = I->second;209EndOff = RB.getMappedOffset(EndOff, true);210StartOff = RB.getMappedOffset(StartOff);211212// Adjust the end offset to the end of the last token, instead of being the213// start of the last token.214if (Range.isTokenRange())215EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts);216217// Advance the iterators to the right spot, yay for linear time algorithms.218RewriteBuffer::iterator Start = RB.begin();219std::advance(Start, StartOff);220RewriteBuffer::iterator End = Start;221assert(EndOff >= StartOff && "Invalid iteration distance");222std::advance(End, EndOff-StartOff);223224return std::string(Start, End);225}226227unsigned Rewriter::getLocationOffsetAndFileID(SourceLocation Loc,228FileID &FID) const {229assert(Loc.isValid() && "Invalid location");230std::pair<FileID, unsigned> V = SourceMgr->getDecomposedLoc(Loc);231FID = V.first;232return V.second;233}234235/// getEditBuffer - Get or create a RewriteBuffer for the specified FileID.236RewriteBuffer &Rewriter::getEditBuffer(FileID FID) {237std::map<FileID, RewriteBuffer>::iterator I =238RewriteBuffers.lower_bound(FID);239if (I != RewriteBuffers.end() && I->first == FID)240return I->second;241I = RewriteBuffers.insert(I, std::make_pair(FID, RewriteBuffer()));242243StringRef MB = SourceMgr->getBufferData(FID);244I->second.Initialize(MB.begin(), MB.end());245246return I->second;247}248249/// InsertText - Insert the specified string at the specified location in the250/// original buffer.251bool Rewriter::InsertText(SourceLocation Loc, StringRef Str,252bool InsertAfter, bool indentNewLines) {253if (!isRewritable(Loc)) return true;254FileID FID;255unsigned StartOffs = getLocationOffsetAndFileID(Loc, FID);256257SmallString<128> indentedStr;258if (indentNewLines && Str.contains('\n')) {259StringRef MB = SourceMgr->getBufferData(FID);260261unsigned lineNo = SourceMgr->getLineNumber(FID, StartOffs) - 1;262const SrcMgr::ContentCache *Content =263&SourceMgr->getSLocEntry(FID).getFile().getContentCache();264unsigned lineOffs = Content->SourceLineCache[lineNo];265266// Find the whitespace at the start of the line.267StringRef indentSpace;268{269unsigned i = lineOffs;270while (isWhitespaceExceptNL(MB[i]))271++i;272indentSpace = MB.substr(lineOffs, i-lineOffs);273}274275SmallVector<StringRef, 4> lines;276Str.split(lines, "\n");277278for (unsigned i = 0, e = lines.size(); i != e; ++i) {279indentedStr += lines[i];280if (i < e-1) {281indentedStr += '\n';282indentedStr += indentSpace;283}284}285Str = indentedStr.str();286}287288getEditBuffer(FID).InsertText(StartOffs, Str, InsertAfter);289return false;290}291292bool Rewriter::InsertTextAfterToken(SourceLocation Loc, StringRef Str) {293if (!isRewritable(Loc)) return true;294FileID FID;295unsigned StartOffs = getLocationOffsetAndFileID(Loc, FID);296RewriteOptions rangeOpts;297rangeOpts.IncludeInsertsAtBeginOfRange = false;298StartOffs += getRangeSize(SourceRange(Loc, Loc), rangeOpts);299getEditBuffer(FID).InsertText(StartOffs, Str, /*InsertAfter*/true);300return false;301}302303/// RemoveText - Remove the specified text region.304bool Rewriter::RemoveText(SourceLocation Start, unsigned Length,305RewriteOptions opts) {306if (!isRewritable(Start)) return true;307FileID FID;308unsigned StartOffs = getLocationOffsetAndFileID(Start, FID);309getEditBuffer(FID).RemoveText(StartOffs, Length, opts.RemoveLineIfEmpty);310return false;311}312313/// ReplaceText - This method replaces a range of characters in the input314/// buffer with a new string. This is effectively a combined "remove/insert"315/// operation.316bool Rewriter::ReplaceText(SourceLocation Start, unsigned OrigLength,317StringRef NewStr) {318if (!isRewritable(Start)) return true;319FileID StartFileID;320unsigned StartOffs = getLocationOffsetAndFileID(Start, StartFileID);321322getEditBuffer(StartFileID).ReplaceText(StartOffs, OrigLength, NewStr);323return false;324}325326bool Rewriter::ReplaceText(SourceRange range, SourceRange replacementRange) {327if (!isRewritable(range.getBegin())) return true;328if (!isRewritable(range.getEnd())) return true;329if (replacementRange.isInvalid()) return true;330SourceLocation start = range.getBegin();331unsigned origLength = getRangeSize(range);332unsigned newLength = getRangeSize(replacementRange);333FileID FID;334unsigned newOffs = getLocationOffsetAndFileID(replacementRange.getBegin(),335FID);336StringRef MB = SourceMgr->getBufferData(FID);337return ReplaceText(start, origLength, MB.substr(newOffs, newLength));338}339340bool Rewriter::IncreaseIndentation(CharSourceRange range,341SourceLocation parentIndent) {342if (range.isInvalid()) return true;343if (!isRewritable(range.getBegin())) return true;344if (!isRewritable(range.getEnd())) return true;345if (!isRewritable(parentIndent)) return true;346347FileID StartFileID, EndFileID, parentFileID;348unsigned StartOff, EndOff, parentOff;349350StartOff = getLocationOffsetAndFileID(range.getBegin(), StartFileID);351EndOff = getLocationOffsetAndFileID(range.getEnd(), EndFileID);352parentOff = getLocationOffsetAndFileID(parentIndent, parentFileID);353354if (StartFileID != EndFileID || StartFileID != parentFileID)355return true;356if (StartOff > EndOff)357return true;358359FileID FID = StartFileID;360StringRef MB = SourceMgr->getBufferData(FID);361362unsigned parentLineNo = SourceMgr->getLineNumber(FID, parentOff) - 1;363unsigned startLineNo = SourceMgr->getLineNumber(FID, StartOff) - 1;364unsigned endLineNo = SourceMgr->getLineNumber(FID, EndOff) - 1;365366const SrcMgr::ContentCache *Content =367&SourceMgr->getSLocEntry(FID).getFile().getContentCache();368369// Find where the lines start.370unsigned parentLineOffs = Content->SourceLineCache[parentLineNo];371unsigned startLineOffs = Content->SourceLineCache[startLineNo];372373// Find the whitespace at the start of each line.374StringRef parentSpace, startSpace;375{376unsigned i = parentLineOffs;377while (isWhitespaceExceptNL(MB[i]))378++i;379parentSpace = MB.substr(parentLineOffs, i-parentLineOffs);380381i = startLineOffs;382while (isWhitespaceExceptNL(MB[i]))383++i;384startSpace = MB.substr(startLineOffs, i-startLineOffs);385}386if (parentSpace.size() >= startSpace.size())387return true;388if (!startSpace.starts_with(parentSpace))389return true;390391StringRef indent = startSpace.substr(parentSpace.size());392393// Indent the lines between start/end offsets.394RewriteBuffer &RB = getEditBuffer(FID);395for (unsigned lineNo = startLineNo; lineNo <= endLineNo; ++lineNo) {396unsigned offs = Content->SourceLineCache[lineNo];397unsigned i = offs;398while (isWhitespaceExceptNL(MB[i]))399++i;400StringRef origIndent = MB.substr(offs, i-offs);401if (origIndent.starts_with(startSpace))402RB.InsertText(offs, indent, /*InsertAfter=*/false);403}404405return false;406}407408bool Rewriter::overwriteChangedFiles() {409bool AllWritten = true;410auto& Diag = getSourceMgr().getDiagnostics();411unsigned OverwriteFailure = Diag.getCustomDiagID(412DiagnosticsEngine::Error, "unable to overwrite file %0: %1");413for (buffer_iterator I = buffer_begin(), E = buffer_end(); I != E; ++I) {414OptionalFileEntryRef Entry = getSourceMgr().getFileEntryRefForID(I->first);415llvm::SmallString<128> Path(Entry->getName());416getSourceMgr().getFileManager().makeAbsolutePath(Path);417if (auto Error = llvm::writeToOutput(Path, [&](llvm::raw_ostream &OS) {418I->second.write(OS);419return llvm::Error::success();420})) {421Diag.Report(OverwriteFailure)422<< Entry->getName() << llvm::toString(std::move(Error));423AllWritten = false;424}425}426return !AllWritten;427}428429430