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