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