1 //===- Commit.cpp - A unit of edits ---------------------------------------===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
10 #include "clang/Edit/Commit.h"
11 #include "clang/Basic/LLVM.h"
12 #include "clang/Basic/SourceLocation.h"
13 #include "clang/Basic/SourceManager.h"
14 #include "clang/Edit/EditedSource.h"
15 #include "clang/Edit/FileOffset.h"
16 #include "clang/Lex/Lexer.h"
17 #include "clang/Lex/PPConditionalDirectiveRecord.h"
18 #include "llvm/ADT/StringRef.h"
22 using namespace clang;
25 SourceLocation Commit::Edit::getFileLocation(SourceManager &SM) const {
26 SourceLocation Loc = SM.getLocForStartOfFile(Offset.getFID());
27 Loc = Loc.getLocWithOffset(Offset.getOffset());
28 assert(Loc.isFileID());
32 CharSourceRange Commit::Edit::getFileRange(SourceManager &SM) const {
33 SourceLocation Loc = getFileLocation(SM);
34 return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length));
37 CharSourceRange Commit::Edit::getInsertFromRange(SourceManager &SM) const {
38 SourceLocation Loc = SM.getLocForStartOfFile(InsertFromRangeOffs.getFID());
39 Loc = Loc.getLocWithOffset(InsertFromRangeOffs.getOffset());
40 assert(Loc.isFileID());
41 return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length));
44 Commit::Commit(EditedSource &Editor)
45 : SourceMgr(Editor.getSourceManager()), LangOpts(Editor.getLangOpts()),
46 PPRec(Editor.getPPCondDirectiveRecord()),
49 bool Commit::insert(SourceLocation loc, StringRef text,
50 bool afterToken, bool beforePreviousInsertions) {
55 if ((!afterToken && !canInsert(loc, Offs)) ||
56 ( afterToken && !canInsertAfterToken(loc, Offs, loc))) {
61 addInsert(loc, Offs, text, beforePreviousInsertions);
65 bool Commit::insertFromRange(SourceLocation loc,
66 CharSourceRange range,
67 bool afterToken, bool beforePreviousInsertions) {
70 if (!canRemoveRange(range, RangeOffs, RangeLen)) {
76 if ((!afterToken && !canInsert(loc, Offs)) ||
77 ( afterToken && !canInsertAfterToken(loc, Offs, loc))) {
83 PPRec->areInDifferentConditionalDirectiveRegion(loc, range.getBegin())) {
88 addInsertFromRange(loc, Offs, RangeOffs, RangeLen, beforePreviousInsertions);
92 bool Commit::remove(CharSourceRange range) {
95 if (!canRemoveRange(range, Offs, Len)) {
100 addRemove(range.getBegin(), Offs, Len);
104 bool Commit::insertWrap(StringRef before, CharSourceRange range,
106 bool commitableBefore = insert(range.getBegin(), before, /*afterToken=*/false,
107 /*beforePreviousInsertions=*/true);
108 bool commitableAfter;
109 if (range.isTokenRange())
110 commitableAfter = insertAfterToken(range.getEnd(), after);
112 commitableAfter = insert(range.getEnd(), after);
114 return commitableBefore && commitableAfter;
117 bool Commit::replace(CharSourceRange range, StringRef text) {
119 return remove(range);
123 if (!canInsert(range.getBegin(), Offs) || !canRemoveRange(range, Offs, Len)) {
124 IsCommitable = false;
128 addRemove(range.getBegin(), Offs, Len);
129 addInsert(range.getBegin(), Offs, text, false);
133 bool Commit::replaceWithInner(CharSourceRange range,
134 CharSourceRange replacementRange) {
135 FileOffset OuterBegin;
137 if (!canRemoveRange(range, OuterBegin, OuterLen)) {
138 IsCommitable = false;
142 FileOffset InnerBegin;
144 if (!canRemoveRange(replacementRange, InnerBegin, InnerLen)) {
145 IsCommitable = false;
149 FileOffset OuterEnd = OuterBegin.getWithOffset(OuterLen);
150 FileOffset InnerEnd = InnerBegin.getWithOffset(InnerLen);
151 if (OuterBegin.getFID() != InnerBegin.getFID() ||
152 InnerBegin < OuterBegin ||
153 InnerBegin > OuterEnd ||
154 InnerEnd > OuterEnd) {
155 IsCommitable = false;
159 addRemove(range.getBegin(),
160 OuterBegin, InnerBegin.getOffset() - OuterBegin.getOffset());
161 addRemove(replacementRange.getEnd(),
162 InnerEnd, OuterEnd.getOffset() - InnerEnd.getOffset());
166 bool Commit::replaceText(SourceLocation loc, StringRef text,
167 StringRef replacementText) {
168 if (text.empty() || replacementText.empty())
173 if (!canReplaceText(loc, replacementText, Offs, Len)) {
174 IsCommitable = false;
178 addRemove(loc, Offs, Len);
179 addInsert(loc, Offs, text, false);
183 void Commit::addInsert(SourceLocation OrigLoc, FileOffset Offs, StringRef text,
184 bool beforePreviousInsertions) {
189 data.Kind = Act_Insert;
190 data.OrigLoc = OrigLoc;
192 data.Text = text.copy(StrAlloc);
193 data.BeforePrev = beforePreviousInsertions;
194 CachedEdits.push_back(data);
197 void Commit::addInsertFromRange(SourceLocation OrigLoc, FileOffset Offs,
198 FileOffset RangeOffs, unsigned RangeLen,
199 bool beforePreviousInsertions) {
204 data.Kind = Act_InsertFromRange;
205 data.OrigLoc = OrigLoc;
207 data.InsertFromRangeOffs = RangeOffs;
208 data.Length = RangeLen;
209 data.BeforePrev = beforePreviousInsertions;
210 CachedEdits.push_back(data);
213 void Commit::addRemove(SourceLocation OrigLoc,
214 FileOffset Offs, unsigned Len) {
219 data.Kind = Act_Remove;
220 data.OrigLoc = OrigLoc;
223 CachedEdits.push_back(data);
226 bool Commit::canInsert(SourceLocation loc, FileOffset &offs) {
231 isAtStartOfMacroExpansion(loc, &loc);
233 const SourceManager &SM = SourceMgr;
234 loc = SM.getTopMacroCallerLoc(loc);
237 if (!isAtStartOfMacroExpansion(loc, &loc))
240 if (SM.isInSystemHeader(loc))
243 std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc);
244 if (locInfo.first.isInvalid())
246 offs = FileOffset(locInfo.first, locInfo.second);
247 return canInsertInOffset(loc, offs);
250 bool Commit::canInsertAfterToken(SourceLocation loc, FileOffset &offs,
251 SourceLocation &AfterLoc) {
256 SourceLocation spellLoc = SourceMgr.getSpellingLoc(loc);
257 unsigned tokLen = Lexer::MeasureTokenLength(spellLoc, SourceMgr, LangOpts);
258 AfterLoc = loc.getLocWithOffset(tokLen);
261 isAtEndOfMacroExpansion(loc, &loc);
263 const SourceManager &SM = SourceMgr;
264 loc = SM.getTopMacroCallerLoc(loc);
267 if (!isAtEndOfMacroExpansion(loc, &loc))
270 if (SM.isInSystemHeader(loc))
273 loc = Lexer::getLocForEndOfToken(loc, 0, SourceMgr, LangOpts);
277 std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc);
278 if (locInfo.first.isInvalid())
280 offs = FileOffset(locInfo.first, locInfo.second);
281 return canInsertInOffset(loc, offs);
284 bool Commit::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
285 for (const auto &act : CachedEdits)
286 if (act.Kind == Act_Remove) {
287 if (act.Offset.getFID() == Offs.getFID() &&
288 Offs > act.Offset && Offs < act.Offset.getWithOffset(act.Length))
289 return false; // position has been removed.
294 return Editor->canInsertInOffset(OrigLoc, Offs);
297 bool Commit::canRemoveRange(CharSourceRange range,
298 FileOffset &Offs, unsigned &Len) {
299 const SourceManager &SM = SourceMgr;
300 range = Lexer::makeFileCharRange(range, SM, LangOpts);
301 if (range.isInvalid())
304 if (range.getBegin().isMacroID() || range.getEnd().isMacroID())
306 if (SM.isInSystemHeader(range.getBegin()) ||
307 SM.isInSystemHeader(range.getEnd()))
310 if (PPRec && PPRec->rangeIntersectsConditionalDirective(range.getAsRange()))
313 std::pair<FileID, unsigned> beginInfo = SM.getDecomposedLoc(range.getBegin());
314 std::pair<FileID, unsigned> endInfo = SM.getDecomposedLoc(range.getEnd());
315 if (beginInfo.first != endInfo.first ||
316 beginInfo.second > endInfo.second)
319 Offs = FileOffset(beginInfo.first, beginInfo.second);
320 Len = endInfo.second - beginInfo.second;
324 bool Commit::canReplaceText(SourceLocation loc, StringRef text,
325 FileOffset &Offs, unsigned &Len) {
326 assert(!text.empty());
328 if (!canInsert(loc, Offs))
331 // Try to load the file buffer.
332 bool invalidTemp = false;
333 StringRef file = SourceMgr.getBufferData(Offs.getFID(), &invalidTemp);
338 return file.substr(Offs.getOffset()).startswith(text);
341 bool Commit::isAtStartOfMacroExpansion(SourceLocation loc,
342 SourceLocation *MacroBegin) const {
343 return Lexer::isAtStartOfMacroExpansion(loc, SourceMgr, LangOpts, MacroBegin);
346 bool Commit::isAtEndOfMacroExpansion(SourceLocation loc,
347 SourceLocation *MacroEnd) const {
348 return Lexer::isAtEndOfMacroExpansion(loc, SourceMgr, LangOpts, MacroEnd);