xref: /netbsd-src/external/apache2/llvm/dist/clang/lib/Edit/Commit.cpp (revision 7330f729ccf0bd976a06f95fad452fe774fc7fd1)
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