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