348 lines · cpp
1//===- Commit.cpp - A unit of edits ---------------------------------------===//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#include "clang/Edit/Commit.h"10#include "clang/Basic/LLVM.h"11#include "clang/Basic/SourceLocation.h"12#include "clang/Basic/SourceManager.h"13#include "clang/Edit/EditedSource.h"14#include "clang/Edit/FileOffset.h"15#include "clang/Lex/Lexer.h"16#include "clang/Lex/PPConditionalDirectiveRecord.h"17#include "llvm/ADT/StringRef.h"18#include <cassert>19 20using namespace clang;21using namespace edit;22 23SourceLocation Commit::Edit::getFileLocation(SourceManager &SM) const {24 SourceLocation Loc = SM.getLocForStartOfFile(Offset.getFID());25 Loc = Loc.getLocWithOffset(Offset.getOffset());26 assert(Loc.isFileID());27 return Loc;28}29 30CharSourceRange Commit::Edit::getFileRange(SourceManager &SM) const {31 SourceLocation Loc = getFileLocation(SM);32 return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length));33}34 35CharSourceRange Commit::Edit::getInsertFromRange(SourceManager &SM) const {36 SourceLocation Loc = SM.getLocForStartOfFile(InsertFromRangeOffs.getFID());37 Loc = Loc.getLocWithOffset(InsertFromRangeOffs.getOffset());38 assert(Loc.isFileID());39 return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length));40}41 42Commit::Commit(EditedSource &Editor)43 : SourceMgr(Editor.getSourceManager()), LangOpts(Editor.getLangOpts()),44 PPRec(Editor.getPPCondDirectiveRecord()),45 Editor(&Editor) {}46 47bool Commit::insert(SourceLocation loc, StringRef text,48 bool afterToken, bool beforePreviousInsertions) {49 if (text.empty())50 return true;51 52 FileOffset Offs;53 if ((!afterToken && !canInsert(loc, Offs)) ||54 ( afterToken && !canInsertAfterToken(loc, Offs, loc))) {55 IsCommitable = false;56 return false;57 }58 59 addInsert(loc, Offs, text, beforePreviousInsertions);60 return true;61}62 63bool Commit::insertFromRange(SourceLocation loc,64 CharSourceRange range,65 bool afterToken, bool beforePreviousInsertions) {66 FileOffset RangeOffs;67 unsigned RangeLen;68 if (!canRemoveRange(range, RangeOffs, RangeLen)) {69 IsCommitable = false;70 return false;71 }72 73 FileOffset Offs;74 if ((!afterToken && !canInsert(loc, Offs)) ||75 ( afterToken && !canInsertAfterToken(loc, Offs, loc))) {76 IsCommitable = false;77 return false;78 }79 80 if (PPRec &&81 PPRec->areInDifferentConditionalDirectiveRegion(loc, range.getBegin())) {82 IsCommitable = false;83 return false;84 }85 86 addInsertFromRange(loc, Offs, RangeOffs, RangeLen, beforePreviousInsertions);87 return true;88}89 90bool Commit::remove(CharSourceRange range) {91 FileOffset Offs;92 unsigned Len;93 if (!canRemoveRange(range, Offs, Len)) {94 IsCommitable = false;95 return false;96 }97 98 addRemove(range.getBegin(), Offs, Len);99 return true;100}101 102bool Commit::insertWrap(StringRef before, CharSourceRange range,103 StringRef after) {104 bool commitableBefore = insert(range.getBegin(), before, /*afterToken=*/false,105 /*beforePreviousInsertions=*/true);106 bool commitableAfter;107 if (range.isTokenRange())108 commitableAfter = insertAfterToken(range.getEnd(), after);109 else110 commitableAfter = insert(range.getEnd(), after);111 112 return commitableBefore && commitableAfter;113}114 115bool Commit::replace(CharSourceRange range, StringRef text) {116 if (text.empty())117 return remove(range);118 119 FileOffset Offs;120 unsigned Len;121 if (!canInsert(range.getBegin(), Offs) || !canRemoveRange(range, Offs, Len)) {122 IsCommitable = false;123 return false;124 }125 126 addRemove(range.getBegin(), Offs, Len);127 addInsert(range.getBegin(), Offs, text, false);128 return true;129}130 131bool Commit::replaceWithInner(CharSourceRange range,132 CharSourceRange replacementRange) {133 FileOffset OuterBegin;134 unsigned OuterLen;135 if (!canRemoveRange(range, OuterBegin, OuterLen)) {136 IsCommitable = false;137 return false;138 }139 140 FileOffset InnerBegin;141 unsigned InnerLen;142 if (!canRemoveRange(replacementRange, InnerBegin, InnerLen)) {143 IsCommitable = false;144 return false;145 }146 147 FileOffset OuterEnd = OuterBegin.getWithOffset(OuterLen);148 FileOffset InnerEnd = InnerBegin.getWithOffset(InnerLen);149 if (OuterBegin.getFID() != InnerBegin.getFID() ||150 InnerBegin < OuterBegin ||151 InnerBegin > OuterEnd ||152 InnerEnd > OuterEnd) {153 IsCommitable = false;154 return false;155 }156 157 addRemove(range.getBegin(),158 OuterBegin, InnerBegin.getOffset() - OuterBegin.getOffset());159 addRemove(replacementRange.getEnd(),160 InnerEnd, OuterEnd.getOffset() - InnerEnd.getOffset());161 return true;162}163 164bool Commit::replaceText(SourceLocation loc, StringRef text,165 StringRef replacementText) {166 if (text.empty() || replacementText.empty())167 return true;168 169 FileOffset Offs;170 unsigned Len;171 if (!canReplaceText(loc, replacementText, Offs, Len)) {172 IsCommitable = false;173 return false;174 }175 176 addRemove(loc, Offs, Len);177 addInsert(loc, Offs, text, false);178 return true;179}180 181void Commit::addInsert(SourceLocation OrigLoc, FileOffset Offs, StringRef text,182 bool beforePreviousInsertions) {183 if (text.empty())184 return;185 186 Edit data;187 data.Kind = Act_Insert;188 data.OrigLoc = OrigLoc;189 data.Offset = Offs;190 data.Text = text.copy(StrAlloc);191 data.BeforePrev = beforePreviousInsertions;192 CachedEdits.push_back(data);193}194 195void Commit::addInsertFromRange(SourceLocation OrigLoc, FileOffset Offs,196 FileOffset RangeOffs, unsigned RangeLen,197 bool beforePreviousInsertions) {198 if (RangeLen == 0)199 return;200 201 Edit data;202 data.Kind = Act_InsertFromRange;203 data.OrigLoc = OrigLoc;204 data.Offset = Offs;205 data.InsertFromRangeOffs = RangeOffs;206 data.Length = RangeLen;207 data.BeforePrev = beforePreviousInsertions;208 CachedEdits.push_back(data);209}210 211void Commit::addRemove(SourceLocation OrigLoc,212 FileOffset Offs, unsigned Len) {213 if (Len == 0)214 return;215 216 Edit data;217 data.Kind = Act_Remove;218 data.OrigLoc = OrigLoc;219 data.Offset = Offs;220 data.Length = Len;221 CachedEdits.push_back(data);222}223 224bool Commit::canInsert(SourceLocation loc, FileOffset &offs) {225 if (loc.isInvalid())226 return false;227 228 if (loc.isMacroID())229 isAtStartOfMacroExpansion(loc, &loc);230 231 const SourceManager &SM = SourceMgr;232 loc = SM.getTopMacroCallerLoc(loc);233 234 if (loc.isMacroID())235 if (!isAtStartOfMacroExpansion(loc, &loc))236 return false;237 238 if (SM.isInSystemHeader(loc))239 return false;240 241 FileIDAndOffset locInfo = SM.getDecomposedLoc(loc);242 if (locInfo.first.isInvalid())243 return false;244 offs = FileOffset(locInfo.first, locInfo.second);245 return canInsertInOffset(loc, offs);246}247 248bool Commit::canInsertAfterToken(SourceLocation loc, FileOffset &offs,249 SourceLocation &AfterLoc) {250 if (loc.isInvalid())251 252 return false;253 254 SourceLocation spellLoc = SourceMgr.getSpellingLoc(loc);255 unsigned tokLen = Lexer::MeasureTokenLength(spellLoc, SourceMgr, LangOpts);256 AfterLoc = loc.getLocWithOffset(tokLen);257 258 if (loc.isMacroID())259 isAtEndOfMacroExpansion(loc, &loc);260 261 const SourceManager &SM = SourceMgr;262 loc = SM.getTopMacroCallerLoc(loc);263 264 if (loc.isMacroID())265 if (!isAtEndOfMacroExpansion(loc, &loc))266 return false;267 268 if (SM.isInSystemHeader(loc))269 return false;270 271 loc = Lexer::getLocForEndOfToken(loc, 0, SourceMgr, LangOpts);272 if (loc.isInvalid())273 return false;274 275 FileIDAndOffset locInfo = SM.getDecomposedLoc(loc);276 if (locInfo.first.isInvalid())277 return false;278 offs = FileOffset(locInfo.first, locInfo.second);279 return canInsertInOffset(loc, offs);280}281 282bool Commit::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {283 for (const auto &act : CachedEdits)284 if (act.Kind == Act_Remove) {285 if (act.Offset.getFID() == Offs.getFID() &&286 Offs > act.Offset && Offs < act.Offset.getWithOffset(act.Length))287 return false; // position has been removed.288 }289 290 if (!Editor)291 return true;292 return Editor->canInsertInOffset(OrigLoc, Offs);293}294 295bool Commit::canRemoveRange(CharSourceRange range,296 FileOffset &Offs, unsigned &Len) {297 const SourceManager &SM = SourceMgr;298 range = Lexer::makeFileCharRange(range, SM, LangOpts);299 if (range.isInvalid())300 return false;301 302 if (range.getBegin().isMacroID() || range.getEnd().isMacroID())303 return false;304 if (SM.isInSystemHeader(range.getBegin()) ||305 SM.isInSystemHeader(range.getEnd()))306 return false;307 308 if (PPRec && PPRec->rangeIntersectsConditionalDirective(range.getAsRange()))309 return false;310 311 FileIDAndOffset beginInfo = SM.getDecomposedLoc(range.getBegin());312 FileIDAndOffset endInfo = SM.getDecomposedLoc(range.getEnd());313 if (beginInfo.first != endInfo.first ||314 beginInfo.second > endInfo.second)315 return false;316 317 Offs = FileOffset(beginInfo.first, beginInfo.second);318 Len = endInfo.second - beginInfo.second;319 return true;320}321 322bool Commit::canReplaceText(SourceLocation loc, StringRef text,323 FileOffset &Offs, unsigned &Len) {324 assert(!text.empty());325 326 if (!canInsert(loc, Offs))327 return false;328 329 // Try to load the file buffer.330 bool invalidTemp = false;331 StringRef file = SourceMgr.getBufferData(Offs.getFID(), &invalidTemp);332 if (invalidTemp)333 return false;334 335 Len = text.size();336 return file.substr(Offs.getOffset()).starts_with(text);337}338 339bool Commit::isAtStartOfMacroExpansion(SourceLocation loc,340 SourceLocation *MacroBegin) const {341 return Lexer::isAtStartOfMacroExpansion(loc, SourceMgr, LangOpts, MacroBegin);342}343 344bool Commit::isAtEndOfMacroExpansion(SourceLocation loc,345 SourceLocation *MacroEnd) const {346 return Lexer::isAtEndOfMacroExpansion(loc, SourceMgr, LangOpts, MacroEnd);347}348