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