1 //===----- EditedSource.cpp - Collection of source edits ------------------===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
10 #include "clang/Edit/EditedSource.h"
11 #include "clang/Basic/CharInfo.h"
12 #include "clang/Basic/SourceManager.h"
13 #include "clang/Edit/Commit.h"
14 #include "clang/Edit/EditsReceiver.h"
15 #include "clang/Lex/Lexer.h"
16 #include "llvm/ADT/SmallString.h"
17 #include "llvm/ADT/Twine.h"
19 using namespace clang;
22 void EditsReceiver::remove(CharSourceRange range) {
23 replace(range, StringRef());
26 void EditedSource::deconstructMacroArgLoc(SourceLocation Loc,
27 SourceLocation &ExpansionLoc,
28 MacroArgUse &ArgUse) {
29 assert(SourceMgr.isMacroArgExpansion(Loc));
30 SourceLocation DefArgLoc = SourceMgr.getImmediateExpansionRange(Loc).first;
31 SourceLocation ImmediateExpansionLoc =
32 SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
33 ExpansionLoc = ImmediateExpansionLoc;
34 while (SourceMgr.isMacroBodyExpansion(ExpansionLoc))
35 ExpansionLoc = SourceMgr.getImmediateExpansionRange(ExpansionLoc).first;
37 StringRef ArgName = Lexer::getSpelling(SourceMgr.getSpellingLoc(DefArgLoc),
38 Buf, SourceMgr, LangOpts);
39 ArgUse = MacroArgUse{nullptr, SourceLocation(), SourceLocation()};
41 ArgUse = {&IdentTable.get(ArgName), ImmediateExpansionLoc,
42 SourceMgr.getSpellingLoc(DefArgLoc)};
45 void EditedSource::startingCommit() {}
47 void EditedSource::finishedCommit() {
48 for (auto &ExpArg : CurrCommitMacroArgExps) {
49 SourceLocation ExpLoc;
51 std::tie(ExpLoc, ArgUse) = ExpArg;
52 auto &ArgUses = ExpansionToArgMap[ExpLoc.getRawEncoding()];
53 if (std::find(ArgUses.begin(), ArgUses.end(), ArgUse) == ArgUses.end())
54 ArgUses.push_back(ArgUse);
56 CurrCommitMacroArgExps.clear();
59 StringRef EditedSource::copyString(const Twine &twine) {
60 SmallString<128> Data;
61 return copyString(twine.toStringRef(Data));
64 bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
65 FileEditsTy::iterator FA = getActionForOffset(Offs);
66 if (FA != FileEdits.end()) {
67 if (FA->first != Offs)
68 return false; // position has been removed.
71 if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
72 SourceLocation ExpLoc;
74 deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);
75 auto I = ExpansionToArgMap.find(ExpLoc.getRawEncoding());
76 if (I != ExpansionToArgMap.end() &&
77 find_if(I->second, [&](const MacroArgUse &U) {
78 return ArgUse.Identifier == U.Identifier &&
79 std::tie(ArgUse.ImmediateExpansionLoc, ArgUse.UseLoc) !=
80 std::tie(U.ImmediateExpansionLoc, U.UseLoc);
81 }) != I->second.end()) {
82 // Trying to write in a macro argument input that has already been
83 // written by a previous commit for another expansion of the same macro
84 // argument name. For example:
87 // #define MAC(x) ((x)+(x))
91 // A commit modified the macro argument 'a' due to the first '(x)'
92 // expansion inside the macro definition, and a subsequent commit tried
93 // to modify 'a' again for the second '(x)' expansion. The edits of the
94 // second commit will be rejected.
101 bool EditedSource::commitInsert(SourceLocation OrigLoc,
102 FileOffset Offs, StringRef text,
103 bool beforePreviousInsertions) {
104 if (!canInsertInOffset(OrigLoc, Offs))
109 if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
111 SourceLocation ExpLoc;
112 deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);
113 if (ArgUse.Identifier)
114 CurrCommitMacroArgExps.emplace_back(ExpLoc, ArgUse);
117 FileEdit &FA = FileEdits[Offs];
118 if (FA.Text.empty()) {
119 FA.Text = copyString(text);
123 if (beforePreviousInsertions)
124 FA.Text = copyString(Twine(text) + FA.Text);
126 FA.Text = copyString(Twine(FA.Text) + text);
131 bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
133 FileOffset InsertFromRangeOffs, unsigned Len,
134 bool beforePreviousInsertions) {
138 SmallString<128> StrVec;
139 FileOffset BeginOffs = InsertFromRangeOffs;
140 FileOffset EndOffs = BeginOffs.getWithOffset(Len);
141 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
142 if (I != FileEdits.begin())
145 for (; I != FileEdits.end(); ++I) {
146 FileEdit &FA = I->second;
147 FileOffset B = I->first;
148 FileOffset E = B.getWithOffset(FA.RemoveLen);
162 for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
163 FileEdit &FA = I->second;
164 FileOffset B = I->first;
165 FileOffset E = B.getWithOffset(FA.RemoveLen);
168 bool Invalid = false;
169 StringRef text = getSourceText(BeginOffs, B, Invalid);
178 if (BeginOffs < EndOffs) {
179 bool Invalid = false;
180 StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
186 return commitInsert(OrigLoc, Offs, StrVec, beforePreviousInsertions);
189 void EditedSource::commitRemove(SourceLocation OrigLoc,
190 FileOffset BeginOffs, unsigned Len) {
194 FileOffset EndOffs = BeginOffs.getWithOffset(Len);
195 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
196 if (I != FileEdits.begin())
199 for (; I != FileEdits.end(); ++I) {
200 FileEdit &FA = I->second;
201 FileOffset B = I->first;
202 FileOffset E = B.getWithOffset(FA.RemoveLen);
208 FileOffset TopBegin, TopEnd;
209 FileEdit *TopFA = nullptr;
211 if (I == FileEdits.end()) {
212 FileEditsTy::iterator
213 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
214 NewI->second.RemoveLen = Len;
218 FileEdit &FA = I->second;
219 FileOffset B = I->first;
220 FileOffset E = B.getWithOffset(FA.RemoveLen);
222 FileEditsTy::iterator
223 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
224 TopBegin = BeginOffs;
226 TopFA = &NewI->second;
227 TopFA->RemoveLen = Len;
232 if (TopEnd >= EndOffs)
234 unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
236 TopFA->RemoveLen += diff;
238 TopFA->Text = StringRef();
242 while (I != FileEdits.end()) {
243 FileEdit &FA = I->second;
244 FileOffset B = I->first;
245 FileOffset E = B.getWithOffset(FA.RemoveLen);
251 FileEdits.erase(I++);
256 unsigned diff = E.getOffset() - TopEnd.getOffset();
258 TopFA->RemoveLen += diff;
266 bool EditedSource::commit(const Commit &commit) {
267 if (!commit.isCommitable())
271 EditedSource &Editor;
272 CommitRAII(EditedSource &Editor) : Editor(Editor) {
273 Editor.startingCommit();
276 Editor.finishedCommit();
280 for (edit::Commit::edit_iterator
281 I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
282 const edit::Commit::Edit &edit = *I;
284 case edit::Commit::Act_Insert:
285 commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
287 case edit::Commit::Act_InsertFromRange:
288 commitInsertFromRange(edit.OrigLoc, edit.Offset,
289 edit.InsertFromRangeOffs, edit.Length,
292 case edit::Commit::Act_Remove:
293 commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
301 // \brief Returns true if it is ok to make the two given characters adjacent.
302 static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
303 // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
304 // making two '<' adjacent.
305 return !(Lexer::isIdentifierBodyChar(left, LangOpts) &&
306 Lexer::isIdentifierBodyChar(right, LangOpts));
309 /// \brief Returns true if it is ok to eliminate the trailing whitespace between
310 /// the given characters.
311 static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
312 const LangOptions &LangOpts) {
313 if (!canBeJoined(left, right, LangOpts))
315 if (isWhitespace(left) || isWhitespace(right))
317 if (canBeJoined(beforeWSpace, right, LangOpts))
318 return false; // the whitespace was intentional, keep it.
322 /// \brief Check the range that we are going to remove and:
323 /// -Remove any trailing whitespace if possible.
324 /// -Insert a space if removing the range is going to mess up the source tokens.
325 static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
326 SourceLocation Loc, FileOffset offs,
327 unsigned &len, StringRef &text) {
328 assert(len && text.empty());
329 SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
330 if (BeginTokLoc != Loc)
331 return; // the range is not at the beginning of a token, keep the range.
333 bool Invalid = false;
334 StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
338 unsigned begin = offs.getOffset();
339 unsigned end = begin + len;
341 // Do not try to extend the removal if we're at the end of the buffer already.
342 if (end == buffer.size())
345 assert(begin < buffer.size() && end < buffer.size() && "Invalid range!");
347 // FIXME: Remove newline.
350 if (buffer[end] == ' ')
355 if (buffer[end] == ' ') {
356 assert((end + 1 != buffer.size() || buffer.data()[end + 1] == 0) &&
357 "buffer not zero-terminated!");
358 if (canRemoveWhitespace(/*left=*/buffer[begin-1],
359 /*beforeWSpace=*/buffer[end-1],
360 /*right=*/buffer.data()[end + 1], // zero-terminated
366 if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
370 static void applyRewrite(EditsReceiver &receiver,
371 StringRef text, FileOffset offs, unsigned len,
372 const SourceManager &SM, const LangOptions &LangOpts,
373 bool shouldAdjustRemovals) {
374 assert(offs.getFID().isValid());
375 SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
376 Loc = Loc.getLocWithOffset(offs.getOffset());
377 assert(Loc.isFileID());
379 if (text.empty() && shouldAdjustRemovals)
380 adjustRemoval(SM, LangOpts, Loc, offs, len, text);
382 CharSourceRange range = CharSourceRange::getCharRange(Loc,
383 Loc.getLocWithOffset(len));
387 receiver.remove(range);
392 receiver.replace(range, text);
394 receiver.insert(Loc, text);
397 void EditedSource::applyRewrites(EditsReceiver &receiver,
398 bool shouldAdjustRemovals) {
399 SmallString<128> StrVec;
400 FileOffset CurOffs, CurEnd;
403 if (FileEdits.empty())
406 FileEditsTy::iterator I = FileEdits.begin();
408 StrVec = I->second.Text;
409 CurLen = I->second.RemoveLen;
410 CurEnd = CurOffs.getWithOffset(CurLen);
413 for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
414 FileOffset offs = I->first;
415 FileEdit act = I->second;
416 assert(offs >= CurEnd);
418 if (offs == CurEnd) {
420 CurLen += act.RemoveLen;
421 CurEnd.getWithOffset(act.RemoveLen);
425 applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,
426 shouldAdjustRemovals);
429 CurLen = act.RemoveLen;
430 CurEnd = CurOffs.getWithOffset(CurLen);
433 applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,
434 shouldAdjustRemovals);
437 void EditedSource::clearRewrites() {
442 StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
444 assert(BeginOffs.getFID() == EndOffs.getFID());
445 assert(BeginOffs <= EndOffs);
446 SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
447 BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
448 assert(BLoc.isFileID());
450 ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
451 return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
452 SourceMgr, LangOpts, &Invalid);
455 EditedSource::FileEditsTy::iterator
456 EditedSource::getActionForOffset(FileOffset Offs) {
457 FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
458 if (I == FileEdits.begin())
459 return FileEdits.end();
461 FileEdit &FA = I->second;
462 FileOffset B = I->first;
463 FileOffset E = B.getWithOffset(FA.RemoveLen);
464 if (Offs >= B && Offs < E)
467 return FileEdits.end();