xref: /llvm-project/clang/lib/Edit/EditedSource.cpp (revision f857950d391d06d490e2ecf014678b4dee24003f)
1 //===----- EditedSource.cpp - Collection of source edits ------------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "clang/Edit/EditedSource.h"
11 #include "clang/Basic/SourceManager.h"
12 #include "clang/Edit/Commit.h"
13 #include "clang/Edit/EditsReceiver.h"
14 #include "clang/Lex/Lexer.h"
15 #include "llvm/ADT/SmallString.h"
16 #include "llvm/ADT/Twine.h"
17 #include <cctype>
18 
19 using namespace clang;
20 using namespace edit;
21 
22 void EditsReceiver::remove(CharSourceRange range) {
23   replace(range, StringRef());
24 }
25 
26 StringRef EditedSource::copyString(const Twine &twine) {
27   SmallString<128> Data;
28   return copyString(twine.toStringRef(Data));
29 }
30 
31 bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
32   FileEditsTy::iterator FA = getActionForOffset(Offs);
33   if (FA != FileEdits.end()) {
34     if (FA->first != Offs)
35       return false; // position has been removed.
36   }
37 
38   if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
39     SourceLocation
40       DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
41     SourceLocation
42       ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
43     llvm::DenseMap<unsigned, SourceLocation>::iterator
44       I = ExpansionToArgMap.find(ExpLoc.getRawEncoding());
45     if (I != ExpansionToArgMap.end() && I->second != DefArgLoc)
46       return false; // Trying to write in a macro argument input that has
47                  // already been written for another argument of the same macro.
48   }
49 
50   return true;
51 }
52 
53 bool EditedSource::commitInsert(SourceLocation OrigLoc,
54                                 FileOffset Offs, StringRef text,
55                                 bool beforePreviousInsertions) {
56   if (!canInsertInOffset(OrigLoc, Offs))
57     return false;
58   if (text.empty())
59     return true;
60 
61   if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
62     SourceLocation
63       DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
64     SourceLocation
65       ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
66     ExpansionToArgMap[ExpLoc.getRawEncoding()] = DefArgLoc;
67   }
68 
69   FileEdit &FA = FileEdits[Offs];
70   if (FA.Text.empty()) {
71     FA.Text = copyString(text);
72     return true;
73   }
74 
75   Twine concat;
76   if (beforePreviousInsertions)
77     concat = Twine(text) + FA.Text;
78   else
79     concat = Twine(FA.Text) +  text;
80 
81   FA.Text = copyString(concat);
82   return true;
83 }
84 
85 bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
86                                    FileOffset Offs,
87                                    FileOffset InsertFromRangeOffs, unsigned Len,
88                                    bool beforePreviousInsertions) {
89   if (Len == 0)
90     return true;
91 
92   SmallString<128> StrVec;
93   FileOffset BeginOffs = InsertFromRangeOffs;
94   FileOffset EndOffs = BeginOffs.getWithOffset(Len);
95   FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
96   if (I != FileEdits.begin())
97     --I;
98 
99   for (; I != FileEdits.end(); ++I) {
100     FileEdit &FA = I->second;
101     FileOffset B = I->first;
102     FileOffset E = B.getWithOffset(FA.RemoveLen);
103 
104     if (BeginOffs == B)
105       break;
106 
107     if (BeginOffs < E) {
108       if (BeginOffs > B) {
109         BeginOffs = E;
110         ++I;
111       }
112       break;
113     }
114   }
115 
116   for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
117     FileEdit &FA = I->second;
118     FileOffset B = I->first;
119     FileOffset E = B.getWithOffset(FA.RemoveLen);
120 
121     if (BeginOffs < B) {
122       bool Invalid = false;
123       StringRef text = getSourceText(BeginOffs, B, Invalid);
124       if (Invalid)
125         return false;
126       StrVec += text;
127     }
128     StrVec += FA.Text;
129     BeginOffs = E;
130   }
131 
132   if (BeginOffs < EndOffs) {
133     bool Invalid = false;
134     StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
135     if (Invalid)
136       return false;
137     StrVec += text;
138   }
139 
140   return commitInsert(OrigLoc, Offs, StrVec.str(), beforePreviousInsertions);
141 }
142 
143 void EditedSource::commitRemove(SourceLocation OrigLoc,
144                                 FileOffset BeginOffs, unsigned Len) {
145   if (Len == 0)
146     return;
147 
148   FileOffset EndOffs = BeginOffs.getWithOffset(Len);
149   FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
150   if (I != FileEdits.begin())
151     --I;
152 
153   for (; I != FileEdits.end(); ++I) {
154     FileEdit &FA = I->second;
155     FileOffset B = I->first;
156     FileOffset E = B.getWithOffset(FA.RemoveLen);
157 
158     if (BeginOffs < E)
159       break;
160   }
161 
162   FileOffset TopBegin, TopEnd;
163   FileEdit *TopFA = 0;
164 
165   if (I == FileEdits.end()) {
166     FileEditsTy::iterator
167       NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
168     NewI->second.RemoveLen = Len;
169     return;
170   }
171 
172   FileEdit &FA = I->second;
173   FileOffset B = I->first;
174   FileOffset E = B.getWithOffset(FA.RemoveLen);
175   if (BeginOffs < B) {
176     FileEditsTy::iterator
177       NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
178     TopBegin = BeginOffs;
179     TopEnd = EndOffs;
180     TopFA = &NewI->second;
181     TopFA->RemoveLen = Len;
182   } else {
183     TopBegin = B;
184     TopEnd = E;
185     TopFA = &I->second;
186     if (TopEnd >= EndOffs)
187       return;
188     unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
189     TopEnd = EndOffs;
190     TopFA->RemoveLen += diff;
191     ++I;
192   }
193 
194   while (I != FileEdits.end()) {
195     FileEdit &FA = I->second;
196     FileOffset B = I->first;
197     FileOffset E = B.getWithOffset(FA.RemoveLen);
198 
199     if (B >= TopEnd)
200       break;
201 
202     if (E <= TopEnd) {
203       FileEdits.erase(I++);
204       continue;
205     }
206 
207     if (B < TopEnd) {
208       unsigned diff = E.getOffset() - TopEnd.getOffset();
209       TopEnd = E;
210       TopFA->RemoveLen += diff;
211       FileEdits.erase(I);
212     }
213 
214     break;
215   }
216 }
217 
218 bool EditedSource::commit(const Commit &commit) {
219   if (!commit.isCommitable())
220     return false;
221 
222   for (edit::Commit::edit_iterator
223          I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
224     const edit::Commit::Edit &edit = *I;
225     switch (edit.Kind) {
226     case edit::Commit::Act_Insert:
227       commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
228       break;
229     case edit::Commit::Act_InsertFromRange:
230       commitInsertFromRange(edit.OrigLoc, edit.Offset,
231                             edit.InsertFromRangeOffs, edit.Length,
232                             edit.BeforePrev);
233       break;
234     case edit::Commit::Act_Remove:
235       commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
236       break;
237     }
238   }
239 
240   return true;
241 }
242 
243 static inline bool isIdentifierChar(char c, const LangOptions &LangOpts) {
244   return std::isalnum(c) || c == '_' || (c == '$' && LangOpts.DollarIdents);
245 }
246 
247 // \brief Returns true if it is ok to make the two given characters adjacent.
248 static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
249   // FIXME: Should use the Lexer to make sure we don't allow stuff like
250   // making two '<' adjacent.
251   return !(isIdentifierChar(left, LangOpts) &&
252            isIdentifierChar(right, LangOpts));
253 }
254 
255 /// \brief Returns true if it is ok to eliminate the trailing whitespace between
256 /// the given characters.
257 static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
258                                 const LangOptions &LangOpts) {
259   if (!canBeJoined(left, right, LangOpts))
260     return false;
261   if (std::isspace(left) || std::isspace(right))
262     return true;
263   if (canBeJoined(beforeWSpace, right, LangOpts))
264     return false; // the whitespace was intentional, keep it.
265   return true;
266 }
267 
268 /// \brief Check the range that we are going to remove and:
269 /// -Remove any trailing whitespace if possible.
270 /// -Insert a space if removing the range is going to mess up the source tokens.
271 static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
272                           SourceLocation Loc, FileOffset offs,
273                           unsigned &len, StringRef &text) {
274   assert(len && text.empty());
275   SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
276   if (BeginTokLoc != Loc)
277     return; // the range is not at the beginning of a token, keep the range.
278 
279   bool Invalid = false;
280   StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
281   if (Invalid)
282     return;
283 
284   unsigned begin = offs.getOffset();
285   unsigned end = begin + len;
286 
287   // FIXME: Remove newline.
288 
289   if (begin == 0) {
290     if (buffer[end] == ' ')
291       ++len;
292     return;
293   }
294 
295   if (buffer[end] == ' ') {
296     if (canRemoveWhitespace(/*left=*/buffer[begin-1],
297                             /*beforeWSpace=*/buffer[end-1],
298                             /*right=*/buffer[end+1],
299                             LangOpts))
300       ++len;
301     return;
302   }
303 
304   if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
305     text = " ";
306 }
307 
308 static void applyRewrite(EditsReceiver &receiver,
309                          StringRef text, FileOffset offs, unsigned len,
310                          const SourceManager &SM, const LangOptions &LangOpts) {
311   assert(!offs.getFID().isInvalid());
312   SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
313   Loc = Loc.getLocWithOffset(offs.getOffset());
314   assert(Loc.isFileID());
315 
316   if (text.empty())
317     adjustRemoval(SM, LangOpts, Loc, offs, len, text);
318 
319   CharSourceRange range = CharSourceRange::getCharRange(Loc,
320                                                      Loc.getLocWithOffset(len));
321 
322   if (text.empty()) {
323     assert(len);
324     receiver.remove(range);
325     return;
326   }
327 
328   if (len)
329     receiver.replace(range, text);
330   else
331     receiver.insert(Loc, text);
332 }
333 
334 void EditedSource::applyRewrites(EditsReceiver &receiver) {
335   SmallString<128> StrVec;
336   FileOffset CurOffs, CurEnd;
337   unsigned CurLen;
338 
339   if (FileEdits.empty())
340     return;
341 
342   FileEditsTy::iterator I = FileEdits.begin();
343   CurOffs = I->first;
344   StrVec = I->second.Text;
345   CurLen = I->second.RemoveLen;
346   CurEnd = CurOffs.getWithOffset(CurLen);
347   ++I;
348 
349   for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
350     FileOffset offs = I->first;
351     FileEdit act = I->second;
352     assert(offs >= CurEnd);
353 
354     if (offs == CurEnd) {
355       StrVec += act.Text;
356       CurLen += act.RemoveLen;
357       CurEnd.getWithOffset(act.RemoveLen);
358       continue;
359     }
360 
361     applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
362     CurOffs = offs;
363     StrVec = act.Text;
364     CurLen = act.RemoveLen;
365     CurEnd = CurOffs.getWithOffset(CurLen);
366   }
367 
368   applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
369 }
370 
371 void EditedSource::clearRewrites() {
372   FileEdits.clear();
373   StrAlloc.Reset();
374 }
375 
376 StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
377                                       bool &Invalid) {
378   assert(BeginOffs.getFID() == EndOffs.getFID());
379   assert(BeginOffs <= EndOffs);
380   SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
381   BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
382   assert(BLoc.isFileID());
383   SourceLocation
384     ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
385   return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
386                               SourceMgr, LangOpts, &Invalid);
387 }
388 
389 EditedSource::FileEditsTy::iterator
390 EditedSource::getActionForOffset(FileOffset Offs) {
391   FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
392   if (I == FileEdits.begin())
393     return FileEdits.end();
394   --I;
395   FileEdit &FA = I->second;
396   FileOffset B = I->first;
397   FileOffset E = B.getWithOffset(FA.RemoveLen);
398   if (Offs >= B && Offs < E)
399     return I;
400 
401   return FileEdits.end();
402 }
403